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