• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Google LLC.
2 // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3 
4 #include "experimental/sorttoy/Cmds.h"
5 #include "experimental/sorttoy/Fake.h"
6 #include "experimental/sorttoy/SortKey.h"
7 
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkGraphics.h"
10 #include "include/gpu/GrDirectContext.h"
11 #include "src/core/SkOSFile.h"
12 #include "src/gpu/GrCaps.h"
13 #include "src/gpu/GrDirectContextPriv.h"
14 #include "src/utils/SkOSPath.h"
15 #include "tools/ToolUtils.h"
16 #include "tools/flags/CommandLineFlags.h"
17 #include "tools/gpu/GrContextFactory.h"
18 
19 #include <algorithm>
20 
21 /*
22  * Questions this is trying to answer:
23  *   How to handle saveLayers (in w/ everything or separate)
24  *   How to handle blurs & other off screen draws
25  *   How to handle clipping
26  *   How does sorting stack up against buckets
27  *   How does creating batches interact w/ the sorting
28  *   How does batching work w/ text
29  *   How does text (esp. atlasing) work at all
30  *   Batching quality vs. existing
31  *   Memory churn/overhead vs existing (esp. wrt batching)
32  *   gpu vs cpu boundedness
33  *
34  * Futher Questions:
35  *   How can we collect uniforms & not store the fps -- seems complicated
36  *   Do all the blend modes (esp. advanced work front-to-back)?
37  *   skgpu::v2 perf vs. skgpu::v1 perf
38  *   Can we prepare any of the saveLayers or off-screen draw render passes in parallel?
39  *
40  * Small potatoes:
41  *   Incorporate CTM into the simulator
42  */
43 
44 /*
45  * How does this all work:
46  *
47  * Each test is specified by a set of RectCmds (which have a unique ID and carry their material
48  * and MC state info) along with the order they are expected to be drawn in with the skgpu::v2.
49  *
50  * To generate an expected image, the RectCmds are replayed into an SkCanvas in the order
51  * provided.
52  *
53  * For the actual (v2) image, the RectCmds are replayed into a FakeCanvas - preserving the
54  * unique ID of the RectCmd. The FakeCanvas creates new RectCmd objects, sorts them using
55  * the SortKey and then performs a kludgey z-buffered rasterization. The FakeCanvas also
56  * preserves the RectCmd order it ultimately used for its rendering and this can be compared
57  * with the expected order from the test.
58  *
59  * The use of the RectCmds to create the tests is a mere convenience to avoid creating a
60  * separate representation of the desired draws.
61  *
62  ***************************
63  * Here are some of the simplifying assumptions of this simulation (and their justification):
64  *
65  * Only SkIRects are used for draws and clips - since MSAA should be taking care of AA for us in
66  * the skgpu::v2 we don't really need SkRects. This also greatly simplifies the z-buffered
67  * rasterization.
68  *
69  **************************
70  * Areas for improvement:
71  *   We should add strokes since there are two distinct drawing methods in the skgpu::v2 (fill v.
72  *   stroke)
73  */
74 
75 using sk_gpu_test::GrContextFactory;
76 
77 static DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs.");
78 
79 static void exitf(const char* format, ...) SK_PRINTF_LIKE(1, 2);
80 
exitf(const char * format,...)81 static void exitf(const char* format, ...) {
82     va_list args;
83     va_start(args, format);
84     vfprintf(stderr, format, args);
85     va_end(args);
86 
87     exit(1);
88 }
89 
save_files(int testID,Shape s,const SkBitmap & expected,const SkBitmap & actual)90 static void save_files(int testID, Shape s, const SkBitmap& expected, const SkBitmap& actual) {
91     if (FLAGS_writePath.isEmpty()) {
92         return;
93     }
94 
95     const char* dir = FLAGS_writePath[0];
96 
97     SkString path = SkOSPath::Join(dir, s == Shape::kRect ? "rect-expected" : "oval-expected");
98     path.appendU32(testID);
99     path.append(".png");
100 
101     if (!sk_mkdir(dir)) {
102         exitf("failed to create directory for png \"%s\"", path.c_str());
103     }
104     if (!ToolUtils::EncodeImageToFile(path.c_str(), expected, SkEncodedImageFormat::kPNG, 100)) {
105         exitf("failed to save png to \"%s\"", path.c_str());
106     }
107 
108     path = SkOSPath::Join(dir, s == Shape::kRect ? "rect-actual" : "oval-actual");
109     path.appendU32(testID);
110     path.append(".png");
111 
112     if (!ToolUtils::EncodeImageToFile(path.c_str(), actual, SkEncodedImageFormat::kPNG, 100)) {
113         exitf("failed to save png to \"%s\"", path.c_str());
114     }
115 }
116 
117 // Exercise basic SortKey behavior
key_test()118 static void key_test() {
119     SortKey k;
120     SkASSERT(!k.transparent());
121     SkASSERT(k.depth() == 0);
122     SkASSERT(k.material() == 0);
123 //    k.dump();
124 
125     SortKey k1(false, 1, 3);
126     SkASSERT(!k1.transparent());
127     SkASSERT(k1.depth() == 1);
128     SkASSERT(k1.material() == 3);
129 //    k1.dump();
130 
131     SortKey k2(true, 2, 1);
132     SkASSERT(k2.transparent());
133     SkASSERT(k2.depth() == 2);
134     SkASSERT(k2.material() == 1);
135 //    k2.dump();
136 }
137 
check_state(FakeMCBlob * actualState,SkIPoint expectedCTM,const std::vector<SkIRect> & expectedClips)138 static void check_state(FakeMCBlob* actualState,
139                         SkIPoint expectedCTM,
140                         const std::vector<SkIRect>& expectedClips) {
141     SkASSERT(actualState->ctm() == expectedCTM);
142 
143     int i = 0;
144     auto states = actualState->mcStates();
145     for (auto& s : states) {
146         for (const sk_sp<ClipCmd>& c : s.cmds()) {
147             SkAssertResult(i < (int) expectedClips.size());
148             SkAssertResult(c->rect() == expectedClips[i]);
149             i++;
150         }
151     }
152 }
153 
154 // Exercise the FakeMCBlob object
mcstack_test()155 static void mcstack_test() {
156     const SkIRect r { 0, 0, 10, 10 };
157     const SkIPoint s1Trans { 10, 10 };
158     const SkIPoint s2TransA { -5, -2 };
159     const SkIPoint s2TransB { -3, -1 };
160 
161     const std::vector<SkIRect> expectedS0Clips;
162     const std::vector<SkIRect> expectedS1Clips {
163         r.makeOffset(s1Trans)
164     };
165     const std::vector<SkIRect> expectedS2aClips {
166         r.makeOffset(s1Trans),
167         r.makeOffset(s2TransA)
168     };
169     const std::vector<SkIRect> expectedS2bClips {
170         r.makeOffset(s1Trans),
171         r.makeOffset(s2TransA),
172         r.makeOffset(s2TransA + s2TransB)
173     };
174 
175     //----------------
176     FakeStateTracker s;
177 
178     auto state0 = s.snapState();
179     // The initial state should have no translation & no clip
180     check_state(state0.get(), { 0, 0 }, expectedS0Clips);
181 
182     //----------------
183     s.push();
184     s.translate(s1Trans);
185     s.clip(sk_make_sp<ClipCmd>(ID(1), Shape::kRect, r));
186 
187     auto state1 = s.snapState();
188     check_state(state1.get(), s1Trans, expectedS1Clips);
189 
190     //----------------
191     s.push();
192     s.translate(s2TransA);
193     s.clip(sk_make_sp<ClipCmd>(ID(2), Shape::kRect, r));
194 
195     auto state2a = s.snapState();
196     check_state(state2a.get(), s1Trans + s2TransA, expectedS2aClips);
197 
198     s.translate(s2TransB);
199     s.clip(sk_make_sp<ClipCmd>(ID(3), Shape::kRect, r));
200 
201     auto state2b = s.snapState();
202     check_state(state2b.get(), s1Trans + s2TransA + s2TransB, expectedS2bClips);
203     SkASSERT(state2a != state2b);
204 
205     //----------------
206     s.pop(PaintersOrder(1));
207     auto state3 = s.snapState();
208     check_state(state3.get(), s1Trans, expectedS1Clips);
209     SkASSERT(state1 == state3);
210 
211     //----------------
212     s.pop(PaintersOrder(2));
213     auto state4 = s.snapState();
214     check_state(state4.get(), { 0, 0 }, expectedS0Clips);
215     SkASSERT(state0 == state4);
216 }
217 
check_order(int testID,const std::vector<ID> & actualOrder,const std::vector<ID> & expectedOrder)218 static void check_order(int testID,
219                         const std::vector<ID>& actualOrder,
220                         const std::vector<ID>& expectedOrder) {
221     if (expectedOrder.size() != actualOrder.size()) {
222         exitf("Op count mismatch in test %d. Expected %zu - got %zu\n",
223               testID,
224               expectedOrder.size(),
225               actualOrder.size());
226     }
227 
228     if (expectedOrder != actualOrder) {
229         SkDebugf("order mismatch in test %d:\n", testID);
230         SkDebugf("E %zu: ", expectedOrder.size());
231         for (auto t : expectedOrder) {
232             SkDebugf("%d", t.toInt());
233         }
234         SkDebugf("\n");
235 
236         SkDebugf("A %zu: ", actualOrder.size());
237         for (auto t : actualOrder) {
238             SkDebugf("%d", t.toInt());
239         }
240         SkDebugf("\n");
241     }
242 }
243 
244 typedef int (*PFTest)(std::vector<sk_sp<Cmd>>* test,
245                       Shape shape,
246                       std::vector<ID>* expectedOrder);
247 
sort_test(PFTest testcase)248 static void sort_test(PFTest testcase) {
249 
250     for (Shape s : { Shape::kRect, Shape::kOval }) {
251         std::vector<sk_sp<Cmd>> test;
252         std::vector<ID> expectedOrder;
253         int testID = testcase(&test, s, &expectedOrder);
254 
255 
256         SkBitmap expectedBM;
257         expectedBM.allocPixels(SkImageInfo::MakeN32Premul(256, 256));
258         expectedBM.eraseColor(SK_ColorBLACK);
259         SkCanvas real(expectedBM);
260 
261         SkBitmap actualBM;
262         actualBM.allocPixels(SkImageInfo::MakeN32Premul(256, 256));
263         actualBM.eraseColor(SK_ColorBLACK);
264 
265         FakeCanvas fake(actualBM);
266         for (const sk_sp<Cmd>& c : test) {
267             c->execute(&fake);
268             c->execute(&real);
269         }
270 
271         fake.finalize();
272 
273         std::vector<ID> actualOrder = fake.getOrder();
274         check_order(testID, actualOrder, expectedOrder);
275 
276         save_files(testID, s, expectedBM, actualBM);
277     }
278 }
279 
280 // Simple test - green rect should appear atop the red rect
test1(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)281 static int test1(std::vector<sk_sp<Cmd>>* test,
282                  Shape shape,
283                  std::vector<ID>* expectedOrder) {
284     // front-to-back order bc all opaque
285     expectedOrder->push_back(ID(1));
286     expectedOrder->push_back(ID(0));
287 
288     //---------------------------------------------------------------------------------------------
289     test->push_back(sk_make_sp<SaveCmd>());
290 
291     SkIRect r{0, 0, 100, 100};
292     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
293     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(SK_ColorGREEN)));
294 
295     test->push_back(sk_make_sp<RestoreCmd>());
296     return 1;
297 }
298 
299 // Simple test - blue rect atop green rect atop red rect
test2(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)300 static int test2(std::vector<sk_sp<Cmd>>* test,
301                  Shape shape,
302                  std::vector<ID>* expectedOrder) {
303     // front-to-back order bc all opaque
304     expectedOrder->push_back(ID(2));
305     expectedOrder->push_back(ID(1));
306     expectedOrder->push_back(ID(0));
307 
308     //---------------------------------------------------------------------------------------------
309     test->push_back(sk_make_sp<SaveCmd>());
310 
311     SkIRect r{0, 0, 100, 100};
312     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
313     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(SK_ColorGREEN)));
314     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98), FakePaint(SK_ColorBLUE)));
315 
316     test->push_back(sk_make_sp<RestoreCmd>());
317     return 2;
318 }
319 
320 // Transparency test - opaque blue rect atop transparent green rect atop opaque red rect
test3(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)321 static int test3(std::vector<sk_sp<Cmd>>* test,
322                  Shape shape,
323                  std::vector<ID>* expectedOrder) {
324     // opaque draws are first and are front-to-back. Transparent draw is last.
325     expectedOrder->push_back(ID(2));
326     expectedOrder->push_back(ID(0));
327     expectedOrder->push_back(ID(1));
328 
329     //---------------------------------------------------------------------------------------------
330     test->push_back(sk_make_sp<SaveCmd>());
331 
332     SkIRect r{0, 0, 100, 100};
333     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
334     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(0x8000FF00)));
335     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98), FakePaint(SK_ColorBLUE)));
336 
337     test->push_back(sk_make_sp<RestoreCmd>());
338     return 3;
339 }
340 
341 // Multi-transparency test - transparent blue rect atop transparent green rect atop
342 // transparent red rect
test4(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)343 static int test4(std::vector<sk_sp<Cmd>>* test,
344                  Shape shape,
345                  std::vector<ID>* expectedOrder) {
346     // All in back-to-front order bc they're all transparent
347     expectedOrder->push_back(ID(0));
348     expectedOrder->push_back(ID(1));
349     expectedOrder->push_back(ID(2));
350 
351     //---------------------------------------------------------------------------------------------
352     test->push_back(sk_make_sp<SaveCmd>());
353 
354     SkIRect r{0, 0, 100, 100};
355     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(0x80FF0000)));
356     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(0x8000FF00)));
357     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98), FakePaint(0x800000FF)));
358 
359     test->push_back(sk_make_sp<RestoreCmd>());
360     return 4;
361 }
362 
363 // Multiple opaque materials test
364 // All opaque:
365 //   normal1, linear1, radial1, normal2, linear2, radial2
366 // Which gets sorted to:
367 //   normal2, normal1, linear2, linear1, radial2, radial1
368 // So, front to back w/in each material type.
test5(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)369 static int test5(std::vector<sk_sp<Cmd>>* test,
370                  Shape shape,
371                  std::vector<ID>* expectedOrder) {
372     // Note: This pushes sorting by material above sorting by Z. Thus we'll get less front to
373     // back benefit.
374     expectedOrder->push_back(ID(3));
375     expectedOrder->push_back(ID(0));
376     expectedOrder->push_back(ID(4));
377     expectedOrder->push_back(ID(1));
378     expectedOrder->push_back(ID(5));
379     expectedOrder->push_back(ID(2));
380 
381     //---------------------------------------------------------------------------------------------
382     test->push_back(sk_make_sp<SaveCmd>());
383 
384     FakePaint p;
385 
386     SkIRect r{0, 0, 100, 100};
387     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),     FakePaint(SK_ColorRED)));
388     p.setLinear(SK_ColorGREEN,   SK_ColorWHITE);
389     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48),   p));
390     p.setRadial(SK_ColorBLUE,    SK_ColorBLACK);
391     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98),   p));
392     test->push_back(sk_make_sp<DrawCmd>(ID(3), shape, r.makeOffset(148, 148), FakePaint(SK_ColorCYAN)));
393     p.setLinear(SK_ColorMAGENTA, SK_ColorWHITE);
394     test->push_back(sk_make_sp<DrawCmd>(ID(4), shape, r.makeOffset(148, 8),   p));
395     p.setRadial(SK_ColorYELLOW,  SK_ColorBLACK);
396     test->push_back(sk_make_sp<DrawCmd>(ID(5), shape, r.makeOffset(8, 148),   p));
397 
398     test->push_back(sk_make_sp<RestoreCmd>());
399     return 5;
400 }
401 
402 // simple clipping test - two shapes w/ 1 clip of the opposite shape
test6(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)403 static int test6(std::vector<sk_sp<Cmd>>* test,
404                  Shape shape,
405                  std::vector<ID>* expectedOrder) {
406     // The expected is front to back after the clip
407     expectedOrder->push_back(ID(2));
408     expectedOrder->push_back(ID(1));
409 
410     Shape clipShape = shape == Shape::kRect ? Shape::kOval : Shape::kRect;
411     //---------------------------------------------------------------------------------------------
412     test->push_back(sk_make_sp<SaveCmd>());
413 
414     test->push_back(sk_make_sp<ClipCmd>(ID(0), clipShape, SkIRect::MakeXYWH(28, 28, 40, 40)));
415 
416     SkIRect r{0, 0, 100, 100};
417     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
418     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(48, 48), FakePaint(SK_ColorGREEN)));
419 
420     test->push_back(sk_make_sp<RestoreCmd>());
421     return 6;
422 }
423 
424 // more complicated clipping w/ opaque draws -> should reorder
test7(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)425 static int test7(std::vector<sk_sp<Cmd>>* test,
426                  Shape shape,
427                  std::vector<ID>* expectedOrder) {
428     // The expected is front to back modulated by the two clip states
429     expectedOrder->push_back(ID(7));
430     expectedOrder->push_back(ID(6));
431     expectedOrder->push_back(ID(2));
432     expectedOrder->push_back(ID(1));
433 
434     expectedOrder->push_back(ID(5));
435     expectedOrder->push_back(ID(4));
436 
437     Shape clipShape = shape == Shape::kRect ? Shape::kOval : Shape::kRect;
438     //---------------------------------------------------------------------------------------------
439     test->push_back(sk_make_sp<SaveCmd>());
440     // select the middle third in x
441     test->push_back(sk_make_sp<ClipCmd>(ID(0), clipShape, SkIRect::MakeXYWH(85, 0, 86, 256)));
442 
443     SkIRect r{0, 0, 100, 100};
444     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(8, 8),     FakePaint(SK_ColorRED)));
445     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(48, 48),   FakePaint(SK_ColorGREEN)));
446 
447     test->push_back(sk_make_sp<SaveCmd>());
448     // intersect w/ the middle third in y
449     test->push_back(sk_make_sp<ClipCmd>(ID(3), clipShape, SkIRect::MakeXYWH(0, 85, 256, 86)));
450 
451     test->push_back(sk_make_sp<DrawCmd>(ID(4), shape, r.makeOffset(98, 98),   FakePaint(SK_ColorBLUE)));
452     test->push_back(sk_make_sp<DrawCmd>(ID(5), shape, r.makeOffset(148, 148), FakePaint(SK_ColorCYAN)));
453 
454     test->push_back(sk_make_sp<RestoreCmd>());
455 
456     test->push_back(sk_make_sp<DrawCmd>(ID(6), shape, r.makeOffset(148, 8),   FakePaint(SK_ColorMAGENTA)));
457     test->push_back(sk_make_sp<DrawCmd>(ID(7), shape, r.makeOffset(8, 148),   FakePaint(SK_ColorYELLOW)));
458 
459     test->push_back(sk_make_sp<RestoreCmd>());
460     return 7;
461 }
462 
main(int argc,char ** argv)463 int main(int argc, char** argv) {
464     CommandLineFlags::Parse(argc, argv);
465 
466     SkGraphics::Init();
467 
468     key_test();
469     mcstack_test();
470     sort_test(test1);
471     sort_test(test2);
472     sort_test(test3);
473     sort_test(test4);
474     sort_test(test5);
475     sort_test(test6);
476     sort_test(test7);
477 
478     return 0;
479 }
480