• 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 
exitf(const char * format,...)79 static void exitf(const char* format, ...) {
80     va_list args;
81     va_start(args, format);
82     vfprintf(stderr, format, args);
83     va_end(args);
84 
85     exit(1);
86 }
87 
save_files(int testID,Shape s,const SkBitmap & expected,const SkBitmap & actual)88 static void save_files(int testID, Shape s, const SkBitmap& expected, const SkBitmap& actual) {
89     if (FLAGS_writePath.isEmpty()) {
90         return;
91     }
92 
93     const char* dir = FLAGS_writePath[0];
94 
95     SkString path = SkOSPath::Join(dir, s == Shape::kRect ? "rect-expected" : "oval-expected");
96     path.appendU32(testID);
97     path.append(".png");
98 
99     if (!sk_mkdir(dir)) {
100         exitf("failed to create directory for png \"%s\"", path.c_str());
101     }
102     if (!ToolUtils::EncodeImageToFile(path.c_str(), expected, SkEncodedImageFormat::kPNG, 100)) {
103         exitf("failed to save png to \"%s\"", path.c_str());
104     }
105 
106     path = SkOSPath::Join(dir, s == Shape::kRect ? "rect-actual" : "oval-actual");
107     path.appendU32(testID);
108     path.append(".png");
109 
110     if (!ToolUtils::EncodeImageToFile(path.c_str(), actual, SkEncodedImageFormat::kPNG, 100)) {
111         exitf("failed to save png to \"%s\"", path.c_str());
112     }
113 }
114 
115 // Exercise basic SortKey behavior
key_test()116 static void key_test() {
117     SortKey k;
118     SkASSERT(!k.transparent());
119     SkASSERT(k.depth() == 0);
120     SkASSERT(k.material() == 0);
121 //    k.dump();
122 
123     SortKey k1(false, 1, 3);
124     SkASSERT(!k1.transparent());
125     SkASSERT(k1.depth() == 1);
126     SkASSERT(k1.material() == 3);
127 //    k1.dump();
128 
129     SortKey k2(true, 2, 1);
130     SkASSERT(k2.transparent());
131     SkASSERT(k2.depth() == 2);
132     SkASSERT(k2.material() == 1);
133 //    k2.dump();
134 }
135 
check_state(FakeMCBlob * actualState,SkIPoint expectedCTM,const std::vector<SkIRect> & expectedClips)136 static void check_state(FakeMCBlob* actualState,
137                         SkIPoint expectedCTM,
138                         const std::vector<SkIRect>& expectedClips) {
139     SkASSERT(actualState->ctm() == expectedCTM);
140 
141     int i = 0;
142     auto states = actualState->mcStates();
143     for (auto& s : states) {
144         for (const sk_sp<ClipCmd>& c : s.cmds()) {
145             SkAssertResult(i < (int) expectedClips.size());
146             SkAssertResult(c->rect() == expectedClips[i]);
147             i++;
148         }
149     }
150 }
151 
152 // Exercise the FakeMCBlob object
mcstack_test()153 static void mcstack_test() {
154     const SkIRect r { 0, 0, 10, 10 };
155     const SkIPoint s1Trans { 10, 10 };
156     const SkIPoint s2TransA { -5, -2 };
157     const SkIPoint s2TransB { -3, -1 };
158 
159     const std::vector<SkIRect> expectedS0Clips;
160     const std::vector<SkIRect> expectedS1Clips {
161         r.makeOffset(s1Trans)
162     };
163     const std::vector<SkIRect> expectedS2aClips {
164         r.makeOffset(s1Trans),
165         r.makeOffset(s2TransA)
166     };
167     const std::vector<SkIRect> expectedS2bClips {
168         r.makeOffset(s1Trans),
169         r.makeOffset(s2TransA),
170         r.makeOffset(s2TransA + s2TransB)
171     };
172 
173     //----------------
174     FakeStateTracker s;
175 
176     auto state0 = s.snapState();
177     // The initial state should have no translation & no clip
178     check_state(state0.get(), { 0, 0 }, expectedS0Clips);
179 
180     //----------------
181     s.push();
182     s.translate(s1Trans);
183     s.clip(sk_make_sp<ClipCmd>(ID(1), Shape::kRect, r));
184 
185     auto state1 = s.snapState();
186     check_state(state1.get(), s1Trans, expectedS1Clips);
187 
188     //----------------
189     s.push();
190     s.translate(s2TransA);
191     s.clip(sk_make_sp<ClipCmd>(ID(2), Shape::kRect, r));
192 
193     auto state2a = s.snapState();
194     check_state(state2a.get(), s1Trans + s2TransA, expectedS2aClips);
195 
196     s.translate(s2TransB);
197     s.clip(sk_make_sp<ClipCmd>(ID(3), Shape::kRect, r));
198 
199     auto state2b = s.snapState();
200     check_state(state2b.get(), s1Trans + s2TransA + s2TransB, expectedS2bClips);
201     SkASSERT(state2a != state2b);
202 
203     //----------------
204     s.pop(PaintersOrder(1));
205     auto state3 = s.snapState();
206     check_state(state3.get(), s1Trans, expectedS1Clips);
207     SkASSERT(state1 == state3);
208 
209     //----------------
210     s.pop(PaintersOrder(2));
211     auto state4 = s.snapState();
212     check_state(state4.get(), { 0, 0 }, expectedS0Clips);
213     SkASSERT(state0 == state4);
214 }
215 
check_order(int testID,const std::vector<ID> & actualOrder,const std::vector<ID> & expectedOrder)216 static void check_order(int testID,
217                         const std::vector<ID>& actualOrder,
218                         const std::vector<ID>& expectedOrder) {
219     if (expectedOrder.size() != actualOrder.size()) {
220         exitf("Op count mismatch in test %d. Expected %d - got %d\n",
221               testID,
222               expectedOrder.size(),
223               actualOrder.size());
224     }
225 
226     if (expectedOrder != actualOrder) {
227         SkDebugf("order mismatch in test %d:\n", testID);
228         SkDebugf("E %zu: ", expectedOrder.size());
229         for (auto t : expectedOrder) {
230             SkDebugf("%d", t.toInt());
231         }
232         SkDebugf("\n");
233 
234         SkDebugf("A %zu: ", actualOrder.size());
235         for (auto t : actualOrder) {
236             SkDebugf("%d", t.toInt());
237         }
238         SkDebugf("\n");
239     }
240 }
241 
242 typedef int (*PFTest)(std::vector<sk_sp<Cmd>>* test,
243                       Shape shape,
244                       std::vector<ID>* expectedOrder);
245 
sort_test(PFTest testcase)246 static void sort_test(PFTest testcase) {
247 
248     for (Shape s : { Shape::kRect, Shape::kOval }) {
249         std::vector<sk_sp<Cmd>> test;
250         std::vector<ID> expectedOrder;
251         int testID = testcase(&test, s, &expectedOrder);
252 
253 
254         SkBitmap expectedBM;
255         expectedBM.allocPixels(SkImageInfo::MakeN32Premul(256, 256));
256         expectedBM.eraseColor(SK_ColorBLACK);
257         SkCanvas real(expectedBM);
258 
259         SkBitmap actualBM;
260         actualBM.allocPixels(SkImageInfo::MakeN32Premul(256, 256));
261         actualBM.eraseColor(SK_ColorBLACK);
262 
263         FakeCanvas fake(actualBM);
264         for (const sk_sp<Cmd>& c : test) {
265             c->execute(&fake);
266             c->execute(&real);
267         }
268 
269         fake.finalize();
270 
271         std::vector<ID> actualOrder = fake.getOrder();
272         check_order(testID, actualOrder, expectedOrder);
273 
274         save_files(testID, s, expectedBM, actualBM);
275     }
276 }
277 
278 // Simple test - green rect should appear atop the red rect
test1(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)279 static int test1(std::vector<sk_sp<Cmd>>* test,
280                  Shape shape,
281                  std::vector<ID>* expectedOrder) {
282     // front-to-back order bc all opaque
283     expectedOrder->push_back(ID(1));
284     expectedOrder->push_back(ID(0));
285 
286     //---------------------------------------------------------------------------------------------
287     test->push_back(sk_make_sp<SaveCmd>());
288 
289     SkIRect r{0, 0, 100, 100};
290     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
291     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(SK_ColorGREEN)));
292 
293     test->push_back(sk_make_sp<RestoreCmd>());
294     return 1;
295 }
296 
297 // Simple test - blue rect atop green rect atop red rect
test2(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)298 static int test2(std::vector<sk_sp<Cmd>>* test,
299                  Shape shape,
300                  std::vector<ID>* expectedOrder) {
301     // front-to-back order bc all opaque
302     expectedOrder->push_back(ID(2));
303     expectedOrder->push_back(ID(1));
304     expectedOrder->push_back(ID(0));
305 
306     //---------------------------------------------------------------------------------------------
307     test->push_back(sk_make_sp<SaveCmd>());
308 
309     SkIRect r{0, 0, 100, 100};
310     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
311     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(SK_ColorGREEN)));
312     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98), FakePaint(SK_ColorBLUE)));
313 
314     test->push_back(sk_make_sp<RestoreCmd>());
315     return 2;
316 }
317 
318 // 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)319 static int test3(std::vector<sk_sp<Cmd>>* test,
320                  Shape shape,
321                  std::vector<ID>* expectedOrder) {
322     // opaque draws are first and are front-to-back. Transparent draw is last.
323     expectedOrder->push_back(ID(2));
324     expectedOrder->push_back(ID(0));
325     expectedOrder->push_back(ID(1));
326 
327     //---------------------------------------------------------------------------------------------
328     test->push_back(sk_make_sp<SaveCmd>());
329 
330     SkIRect r{0, 0, 100, 100};
331     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
332     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(0x8000FF00)));
333     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98), FakePaint(SK_ColorBLUE)));
334 
335     test->push_back(sk_make_sp<RestoreCmd>());
336     return 3;
337 }
338 
339 // Multi-transparency test - transparent blue rect atop transparent green rect atop
340 // transparent red rect
test4(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)341 static int test4(std::vector<sk_sp<Cmd>>* test,
342                  Shape shape,
343                  std::vector<ID>* expectedOrder) {
344     // All in back-to-front order bc they're all transparent
345     expectedOrder->push_back(ID(0));
346     expectedOrder->push_back(ID(1));
347     expectedOrder->push_back(ID(2));
348 
349     //---------------------------------------------------------------------------------------------
350     test->push_back(sk_make_sp<SaveCmd>());
351 
352     SkIRect r{0, 0, 100, 100};
353     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),   FakePaint(0x80FF0000)));
354     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48), FakePaint(0x8000FF00)));
355     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98), FakePaint(0x800000FF)));
356 
357     test->push_back(sk_make_sp<RestoreCmd>());
358     return 4;
359 }
360 
361 // Multiple opaque materials test
362 // All opaque:
363 //   normal1, linear1, radial1, normal2, linear2, radial2
364 // Which gets sorted to:
365 //   normal2, normal1, linear2, linear1, radial2, radial1
366 // So, front to back w/in each material type.
test5(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)367 static int test5(std::vector<sk_sp<Cmd>>* test,
368                  Shape shape,
369                  std::vector<ID>* expectedOrder) {
370     // Note: This pushes sorting by material above sorting by Z. Thus we'll get less front to
371     // back benefit.
372     expectedOrder->push_back(ID(3));
373     expectedOrder->push_back(ID(0));
374     expectedOrder->push_back(ID(4));
375     expectedOrder->push_back(ID(1));
376     expectedOrder->push_back(ID(5));
377     expectedOrder->push_back(ID(2));
378 
379     //---------------------------------------------------------------------------------------------
380     test->push_back(sk_make_sp<SaveCmd>());
381 
382     FakePaint p;
383 
384     SkIRect r{0, 0, 100, 100};
385     test->push_back(sk_make_sp<DrawCmd>(ID(0), shape, r.makeOffset(8, 8),     FakePaint(SK_ColorRED)));
386     p.setLinear(SK_ColorGREEN,   SK_ColorWHITE);
387     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(48, 48),   p));
388     p.setRadial(SK_ColorBLUE,    SK_ColorBLACK);
389     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(98, 98),   p));
390     test->push_back(sk_make_sp<DrawCmd>(ID(3), shape, r.makeOffset(148, 148), FakePaint(SK_ColorCYAN)));
391     p.setLinear(SK_ColorMAGENTA, SK_ColorWHITE);
392     test->push_back(sk_make_sp<DrawCmd>(ID(4), shape, r.makeOffset(148, 8),   p));
393     p.setRadial(SK_ColorYELLOW,  SK_ColorBLACK);
394     test->push_back(sk_make_sp<DrawCmd>(ID(5), shape, r.makeOffset(8, 148),   p));
395 
396     test->push_back(sk_make_sp<RestoreCmd>());
397     return 5;
398 }
399 
400 // 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)401 static int test6(std::vector<sk_sp<Cmd>>* test,
402                  Shape shape,
403                  std::vector<ID>* expectedOrder) {
404     // The expected is front to back after the clip
405     expectedOrder->push_back(ID(2));
406     expectedOrder->push_back(ID(1));
407 
408     Shape clipShape = shape == Shape::kRect ? Shape::kOval : Shape::kRect;
409     //---------------------------------------------------------------------------------------------
410     test->push_back(sk_make_sp<SaveCmd>());
411 
412     test->push_back(sk_make_sp<ClipCmd>(ID(0), clipShape, SkIRect::MakeXYWH(28, 28, 40, 40)));
413 
414     SkIRect r{0, 0, 100, 100};
415     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(8, 8),   FakePaint(SK_ColorRED)));
416     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(48, 48), FakePaint(SK_ColorGREEN)));
417 
418     test->push_back(sk_make_sp<RestoreCmd>());
419     return 6;
420 }
421 
422 // more complicated clipping w/ opaque draws -> should reorder
test7(std::vector<sk_sp<Cmd>> * test,Shape shape,std::vector<ID> * expectedOrder)423 static int test7(std::vector<sk_sp<Cmd>>* test,
424                  Shape shape,
425                  std::vector<ID>* expectedOrder) {
426     // The expected is front to back modulated by the two clip states
427     expectedOrder->push_back(ID(7));
428     expectedOrder->push_back(ID(6));
429     expectedOrder->push_back(ID(2));
430     expectedOrder->push_back(ID(1));
431 
432     expectedOrder->push_back(ID(5));
433     expectedOrder->push_back(ID(4));
434 
435     Shape clipShape = shape == Shape::kRect ? Shape::kOval : Shape::kRect;
436     //---------------------------------------------------------------------------------------------
437     test->push_back(sk_make_sp<SaveCmd>());
438     // select the middle third in x
439     test->push_back(sk_make_sp<ClipCmd>(ID(0), clipShape, SkIRect::MakeXYWH(85, 0, 86, 256)));
440 
441     SkIRect r{0, 0, 100, 100};
442     test->push_back(sk_make_sp<DrawCmd>(ID(1), shape, r.makeOffset(8, 8),     FakePaint(SK_ColorRED)));
443     test->push_back(sk_make_sp<DrawCmd>(ID(2), shape, r.makeOffset(48, 48),   FakePaint(SK_ColorGREEN)));
444 
445     test->push_back(sk_make_sp<SaveCmd>());
446     // intersect w/ the middle third in y
447     test->push_back(sk_make_sp<ClipCmd>(ID(3), clipShape, SkIRect::MakeXYWH(0, 85, 256, 86)));
448 
449     test->push_back(sk_make_sp<DrawCmd>(ID(4), shape, r.makeOffset(98, 98),   FakePaint(SK_ColorBLUE)));
450     test->push_back(sk_make_sp<DrawCmd>(ID(5), shape, r.makeOffset(148, 148), FakePaint(SK_ColorCYAN)));
451 
452     test->push_back(sk_make_sp<RestoreCmd>());
453 
454     test->push_back(sk_make_sp<DrawCmd>(ID(6), shape, r.makeOffset(148, 8),   FakePaint(SK_ColorMAGENTA)));
455     test->push_back(sk_make_sp<DrawCmd>(ID(7), shape, r.makeOffset(8, 148),   FakePaint(SK_ColorYELLOW)));
456 
457     test->push_back(sk_make_sp<RestoreCmd>());
458     return 7;
459 }
460 
main(int argc,char ** argv)461 int main(int argc, char** argv) {
462     CommandLineFlags::Parse(argc, argv);
463 
464     SkGraphics::Init();
465 
466     key_test();
467     mcstack_test();
468     sort_test(test1);
469     sort_test(test2);
470     sort_test(test3);
471     sort_test(test4);
472     sort_test(test5);
473     sort_test(test6);
474     sort_test(test7);
475 
476     return 0;
477 }
478