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
75using sk_gpu_test::GrContextFactory;
76
77static DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs.");
78
79static 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
88static 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
116static 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
136static 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
153static 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
216static 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
242typedef int (*PFTest)(std::vector<sk_sp<Cmd>>* test,
243                      Shape shape,
244                      std::vector<ID>* expectedOrder);
245
246static 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
279static 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
298static 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
319static 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
341static 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.
367static 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
401static 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
423static 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
461int 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