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