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