bl_bench_app.cpp (26406B)
1 // This file is part of Blend2D project <https://blend2d.com> 2 // 3 // See LICENSE.md for license and copyright information 4 // SPDX-License-Identifier: Zlib 5 6 #include <stdio.h> 7 #include <string.h> 8 9 #include <cmath> 10 #include <limits> 11 #include <type_traits> 12 #include <tuple> 13 14 #include "bl_bench_app.h" 15 #include "bl_bench_backend.h" 16 17 #include "images_data.h" 18 19 #define ARRAY_SIZE(X) uint32_t(sizeof(X) / sizeof(X[0])) 20 21 namespace blbench { 22 23 static constexpr uint32_t supported_backends_mask = 24 #if defined(BL_BENCH_ENABLE_AGG) 25 (1u << uint32_t(BackendKind::kAGG)) | 26 #endif 27 #if defined(BL_BENCH_ENABLE_CAIRO) 28 (1u << uint32_t(BackendKind::kCairo)) | 29 #endif 30 #if defined(BL_BENCH_ENABLE_QT) 31 (1u << uint32_t(BackendKind::kQt)) | 32 #endif 33 #if defined(BL_BENCH_ENABLE_SKIA) 34 (1u << uint32_t(BackendKind::kSkia)) | 35 #endif 36 #if defined(BL_BENCH_ENABLE_JUCE) 37 (1u << uint32_t(BackendKind::kJUCE)) | 38 #endif 39 #if defined(BL_BENCH_ENABLE_COREGRAPHICS) 40 (1u << uint32_t(BackendKind::kCoreGraphics)) | 41 #endif 42 (1u << uint32_t(BackendKind::kBlend2D)); 43 44 static const char* backend_kind_name_table[] = { 45 "Blend2D", 46 "AGG", 47 "Cairo", 48 "Qt", 49 "Skia", 50 "JUCE", 51 "CoreGraphics" 52 }; 53 54 static const char* test_kind_name_table[] = { 55 "FillRectA", 56 "FillRectU", 57 "FillRectRot", 58 "FillRoundU", 59 "FillRoundRot", 60 "FillTriangle", 61 "FillPolyNZi10", 62 "FillPolyEOi10", 63 "FillPolyNZi20", 64 "FillPolyEOi20", 65 "FillPolyNZi40", 66 "FillPolyEOi40", 67 "FillButterfly", 68 "FillFish", 69 "FillDragon", 70 "FillWorld", 71 "StrokeRectA", 72 "StrokeRectU", 73 "StrokeRectRot", 74 "StrokeRoundU", 75 "StrokeRoundRot", 76 "StrokeTriangle", 77 "StrokePoly10", 78 "StrokePoly20", 79 "StrokePoly40", 80 "StrokeButterfly", 81 "StrokeFish", 82 "StrokeDragon", 83 "StrokeWorld" 84 }; 85 86 static const char* comp_op_name_table[] = { 87 "SrcOver", 88 "SrcCopy", 89 "SrcIn", 90 "SrcOut", 91 "SrcAtop", 92 "DstOver", 93 "DstCopy", 94 "DstIn", 95 "DstOut", 96 "DstAtop", 97 "Xor", 98 "Clear", 99 "Plus", 100 "Minus", 101 "Modulate", 102 "Multiply", 103 "Screen", 104 "Overlay", 105 "Darken", 106 "Lighten", 107 "ColorDodge", 108 "ColorBurn", 109 "LinearBurn", 110 "LinearLight", 111 "PinLight", 112 "HardLight", 113 "SoftLight", 114 "Difference", 115 "Exclusion" 116 }; 117 118 static const char* style_kind_name_table[] = { 119 "Solid", 120 "Linear@Pad", 121 "Linear@Repeat", 122 "Linear@Reflect", 123 "Radial@Pad", 124 "Radial@Repeat", 125 "Radial@Reflect", 126 "Conic", 127 "Pattern_NN", 128 "Pattern_BI" 129 }; 130 131 static const uint32_t bench_shape_size_table[kBenchShapeSizeCount] = { 132 8, 16, 32, 64, 128, 256 133 }; 134 135 const char bench_border_str[] = "+--------------------+-------------+---------------+----------+----------+----------+----------+----------+----------+\n"; 136 const char bench_header_str[] = "|%-20s" "| CompOp | Style | 8x8 | 16x16 | 32x32 | 64x64 | 128x128 | 256x256 |\n"; 137 const char bench_fata_fmt_str[] = "|%-20s" "| %-12s" "| %-14s" "| %-9s" "| %-9s" "| %-9s" "| %-9s" "| %-9s" "| %-9s" "|\n"; 138 139 static const char* get_os_string() { 140 #if defined(__ANDROID__) 141 return "android"; 142 #elif defined(__linux__) 143 return "linux"; 144 #elif defined(__APPLE__) && defined(__MACH__) 145 return "osx"; 146 #elif defined(__APPLE__) 147 return "apple"; 148 #elif defined(__DragonFly__) 149 return "dragonflybsd"; 150 #elif defined(__FreeBSD__) 151 return "freebsd"; 152 #elif defined(__NetBSD__) 153 return "netbsd"; 154 #elif defined(__OpenBSD__) 155 return "openbsd"; 156 #elif defined(__HAIKU__) 157 return "haiku"; 158 #elif defined(_WIN32) 159 return "windows"; 160 #else 161 return "unknown"; 162 #endif 163 } 164 165 static const char* get_cpu_arch_string() { 166 #if defined(_M_X64) || defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) 167 return "x86_64"; 168 #elif defined(_M_IX86) || defined(__i386) || defined(__i386__) 169 return "x86"; 170 #elif defined(_M_ARM64) || defined(__ARM64__) || defined(__aarch64__) 171 return "aarch64"; 172 #elif defined(_M_ARM) || defined(_M_ARMT) || defined(__arm__) || defined(__thumb__) || defined(__thumb2__) 173 return "aarch32"; 174 #elif defined(_MIPS_ARCH_MIPS64) || defined(__mips64) 175 return "mips64"; 176 #elif defined(_MIPS_ARCH_MIPS32) || defined(_M_MRX000) || defined(__mips) || defined(__mips__) 177 return "mips32"; 178 #elif defined(__riscv) || defined(__riscv__) 179 return sizeof(void*) >= 8 ? "riscv64" : "riscv32"; 180 #elif defined(__PPC64__) || defined(__ppc64__) || defined(_ARCH_PPC64) 181 return "ppc64"; 182 #elif defined(_LOONGARCH_ARCH) || defined(__loongarch_arch) || defined(__loongarch64) 183 return "la64"; 184 #else 185 return "unknown"; 186 #endif 187 } 188 189 static const char* get_format_string(BLFormat format) { 190 switch (format) { 191 case BL_FORMAT_PRGB32: 192 return "prgb32"; 193 194 case BL_FORMAT_XRGB32: 195 return "xrgb32"; 196 197 case BL_FORMAT_A8: 198 return "a8"; 199 200 default: 201 return "unknown"; 202 } 203 } 204 205 static bool strieq(const char* a, const char* b) { 206 size_t aLen = strlen(a); 207 size_t bLen = strlen(b); 208 209 if (aLen != bLen) 210 return false; 211 212 for (size_t i = 0; i < aLen; i++) { 213 unsigned ac = (unsigned char)a[i]; 214 unsigned bc = (unsigned char)b[i]; 215 216 if (ac >= 'A' && ac <= 'Z') ac += uint32_t('a' - 'A'); 217 if (bc >= 'A' && bc <= 'Z') bc += uint32_t('a' - 'A'); 218 219 if (ac != bc) 220 return false; 221 } 222 223 return true; 224 } 225 226 static uint32_t search_string_list(const char** list_data, size_t list_size, const char* key) { 227 for (size_t i = 0; i < list_size; i++) 228 if (strieq(list_data[i], key)) 229 return uint32_t(i); 230 return 0xFFFFFFFFu; 231 } 232 233 static void spacesToUnderscores(char* s) { 234 while (*s) { 235 if (*s == ' ') 236 *s = '_'; 237 s++; 238 } 239 } 240 241 static BLArray<BLString> split_string(const char* s) { 242 BLArray<BLString> arr; 243 while (*s) { 244 const char* end = strchr(s, ','); 245 if (!end) { 246 arr.append(BLString(s)); 247 break; 248 } 249 else { 250 BLString part(BLStringView{s, (size_t)(end - s)}); 251 arr.append(part); 252 s = end + 1; 253 } 254 } 255 return arr; 256 } 257 258 static std::tuple<int, uint32_t> parse_list(const char** list_data, size_t list_size, const char* input_list, const char* parse_error_msg) { 259 int listOp = 0; 260 uint32_t parsedMask = 0; 261 BLArray<BLString> parts = split_string(input_list); 262 263 for (const BLString& part : parts) { 264 if (part.is_empty()) 265 continue; 266 267 const char* p = part.data(); 268 int partOp = 0; 269 270 if (p[0] == '-') { 271 p++; 272 partOp = -1; 273 } 274 else { 275 partOp = 1; 276 } 277 278 if (listOp == 0) { 279 listOp = partOp; 280 } 281 else if (listOp != partOp) { 282 printf("ERROR: %s [%s]: specify either additive or subtractive list, but not both\n", parse_error_msg, input_list); 283 return std::tuple<int, uint32_t>(-2, 0); 284 } 285 286 uint32_t backend_index = search_string_list(list_data, list_size, p); 287 if (backend_index == 0xFFFFFFFFu) { 288 printf("ERROR: %s [%s]: couldn't recognize '%s' part\n", parse_error_msg, input_list, p); 289 return std::tuple<int, uint32_t>(-2, 0); 290 } 291 292 parsedMask |= 1u << backend_index; 293 } 294 295 return std::tuple<int, uint32_t>(listOp, parsedMask); 296 } 297 298 struct DurationFormat { 299 char data[64]; 300 301 inline void format(double cpms) { 302 if (cpms <= 0.1) { 303 snprintf(data, 64, "%0.4f", cpms); 304 } 305 else if (cpms <= 1.0) { 306 snprintf(data, 64, "%0.3f", cpms); 307 } 308 else if (cpms < 10.0) { 309 snprintf(data, 64, "%0.2f", cpms); 310 } 311 else if (cpms < 100.0) { 312 snprintf(data, 64, "%0.1f", cpms); 313 } 314 else { 315 snprintf(data, 64, "%llu", (unsigned long long)std::round(cpms)); 316 } 317 } 318 }; 319 320 BenchApp::BenchApp(int argc, char** argv) 321 : _cmd_line(argc, argv), 322 _backends(supported_backends_mask) {} 323 BenchApp::~BenchApp() {} 324 325 void BenchApp::print_app_info() const { 326 BLRuntimeBuildInfo build_info; 327 BLRuntime::query_build_info(&build_info); 328 329 printf( 330 "Blend2D Benchmarking Tool\n" 331 "\n" 332 "Blend2D Information:\n" 333 " Version : %u.%u.%u\n" 334 " Build Type : %s\n" 335 " Compiled By: %s\n" 336 "\n", 337 build_info.major_version, 338 build_info.minor_version, 339 build_info.patch_version, 340 build_info.build_type == BL_RUNTIME_BUILD_TYPE_DEBUG ? "Debug" : "Release", 341 build_info.compiler_info); 342 343 fflush(stdout); 344 } 345 346 void BenchApp::print_options() const { 347 const char no_yes[][4] = { "no", "yes" }; 348 349 printf( 350 "The following options are supported / used:\n" 351 " --width=N [%u] Canvas width to use for rendering\n" 352 " --height=N [%u] Canvas height to use for rendering\n" 353 " --quantity=N [%d] Render calls per test (0 = adjust depending on test duration)\n" 354 " --size-count=N [%u] Number of size iterations (1=8x8 -> 6=8x8..256x256)\n" 355 " --comp-op=<list> [%s] Benchmark a specific composition operator\n" 356 " --repeat=N [%d] Number of repeats of each test to select the best time\n" 357 " --backends=<list> [%s] Backends to use (use 'a,b' to select few, '-xxx' to disable)\n" 358 " --save-images [%s] Save each generated image independently (use with --quantity)\n" 359 " --save-overview [%s] Save generated images grouped by sizes (use with --quantity)\n" 360 " --deep [%s] More tests that use gradients and textures\n" 361 " --isolated [%s] Use Blend2D isolated context (useful for development only)\n" 362 "\n", 363 _width, 364 _height, 365 _quantity, 366 _size_count, 367 _comp_op == 0xFFFFFFFF ? "all" : comp_op_name_table[_comp_op], 368 _repeat, 369 _backends == supported_backends_mask ? "all" : "...", 370 no_yes[_save_images], 371 no_yes[_save_overview], 372 no_yes[_deep_bench], 373 no_yes[_isolated] 374 ); 375 376 fflush(stdout); 377 } 378 379 void BenchApp::print_backends() const { 380 printf("Backends supported (by default all supported backends are enabled by tests unless overridden):\n"); 381 const char disabled_enabled[][16] = { "disabled", "enabled" }; 382 383 for (uint32_t backend_index = 0; backend_index < kBackendKindCount; backend_index++) { 384 uint32_t backend_mask = 1u << backend_index; 385 386 if (backend_mask & supported_backends_mask) { 387 printf(" - %-15s [%s]\n", 388 backend_kind_name_table[backend_index], 389 disabled_enabled[(_backends & backend_mask) != 0]); 390 } 391 else { 392 printf(" - %-15s [unsupported]\n", 393 backend_kind_name_table[backend_index]); 394 } 395 } 396 397 printf("\n"); 398 fflush(stdout); 399 } 400 401 bool BenchApp::parse_command_line() { 402 _width = _cmd_line.value_as_uint("--width", _width); 403 _height = _cmd_line.value_as_uint("--height", _height); 404 _comp_op = 0xFFFFFFFFu; 405 _size_count = _cmd_line.value_as_uint("--size-count", _size_count); 406 _quantity = _cmd_line.value_as_uint("--quantity", _quantity); 407 _repeat = _cmd_line.value_as_uint("--repeat", _repeat); 408 409 _save_images = _cmd_line.has_arg("--save-images"); 410 _save_overview = _cmd_line.has_arg("--save-overview"); 411 _deep_bench = _cmd_line.has_arg("--deep"); 412 _isolated = _cmd_line.has_arg("--isolated"); 413 414 const char* comp_op_string = _cmd_line.value_of("--comp_op", nullptr); 415 const char* backend_string = _cmd_line.value_of("--backend", nullptr); 416 417 if (_width < 10|| _width > 4096) { 418 printf("ERROR: Invalid --width=%u specified\n", _width); 419 return false; 420 } 421 422 if (_height < 10|| _height > 4096) { 423 printf("ERROR: Invalid --height=%u specified\n", _height); 424 return false; 425 } 426 427 if (_size_count == 0 || _size_count > kBenchShapeSizeCount) { 428 printf("ERROR: Invalid --size-count=%u specified\n", _size_count); 429 return false; 430 } 431 432 if (_quantity > 100000u) { 433 printf("ERROR: Invalid --quantity=%u specified\n", _quantity); 434 return false; 435 } 436 437 if (_repeat <= 0 || _repeat > 100) { 438 printf("ERROR: Invalid --repeat=%u specified\n", _repeat); 439 return false; 440 } 441 442 if (_save_images && !_quantity) { 443 printf("ERROR: Missing --quantity argument; it must be provided when --save-images is used\n"); 444 return false; 445 } 446 447 if (_save_overview && !_quantity) { 448 printf("ERROR: Missing --quantity argument; it must be provided when --save-overview is used\n"); 449 return false; 450 } 451 452 if (comp_op_string && strcmp(comp_op_string, "all") != 0) { 453 _comp_op = search_string_list(comp_op_name_table, ARRAY_SIZE(comp_op_name_table), comp_op_string); 454 if (_comp_op == 0xFFFFFFFF) { 455 printf("ERROR: Invalid composition operator [%s] specified\n", comp_op_string); 456 return false; 457 } 458 } 459 460 if (backend_string && strcmp(backend_string, "all") != 0) { 461 std::tuple<int, uint32_t> v = parse_list(backend_kind_name_table, kBackendKindCount, backend_string, "Invalid --backend list"); 462 463 if (std::get<0>(v) == -2) { 464 return false; 465 } 466 467 if (std::get<0>(v) == 1) { 468 _backends = std::get<1>(v); 469 } 470 else if (std::get<0>(v) == -1) { 471 _backends &= ~std::get<1>(v); 472 } 473 } 474 475 return true; 476 } 477 478 bool BenchApp::init() { 479 if (_cmd_line.has_arg("--help")) { 480 info(); 481 exit(0); 482 } 483 484 if (!parse_command_line()) { 485 info(); 486 exit(1); 487 } 488 489 return read_image(_sprite_data[0], "#0", _resource_babelfish_png, sizeof(_resource_babelfish_png)) && 490 read_image(_sprite_data[1], "#1", _resource_ksplash_png , sizeof(_resource_ksplash_png )) && 491 read_image(_sprite_data[2], "#2", _resource_ktip_png , sizeof(_resource_ktip_png )) && 492 read_image(_sprite_data[3], "#3", _resource_firewall_png , sizeof(_resource_firewall_png )); 493 } 494 495 void BenchApp::info() { 496 BLRuntimeBuildInfo build_info; 497 BLRuntime::query_build_info(&build_info); 498 499 print_app_info(); 500 print_options(); 501 print_backends(); 502 } 503 504 bool BenchApp::read_image(BLImage& image, const char* name, const void* data, size_t size) noexcept { 505 BLResult result = image.read_from_data(data, size); 506 if (result != BL_SUCCESS) { 507 printf("Failed to read an image '%s' used for benchmarking\n", name); 508 return false; 509 } 510 else { 511 return true; 512 } 513 } 514 515 BLImage BenchApp::get_scaled_sprite(uint32_t id, uint32_t size) const { 516 auto it = _scaled_sprites.find(size); 517 if (it != _scaled_sprites.end()) { 518 return it->second[id]; 519 } 520 521 SpriteData scaled; 522 for (uint32_t i = 0; i < kBenchNumSprites; i++) { 523 BLImage::scale( 524 scaled[i], 525 _sprite_data[i], 526 BLSizeI(int(size), int(size)), BL_IMAGE_SCALE_FILTER_BILINEAR); 527 } 528 _scaled_sprites.emplace(size, scaled); 529 return scaled[id]; 530 } 531 532 bool BenchApp::is_backend_enabled(BackendKind backend_kind) const { 533 return (_backends & (1u << uint32_t(backend_kind))) != 0; 534 } 535 536 bool BenchApp::is_style_enabled(StyleKind style) const { 537 if (_deep_bench) 538 return true; 539 540 // If this is not a deep run we just select the following styles to be tested: 541 return style == StyleKind::kSolid || 542 style == StyleKind::kLinearPad || 543 style == StyleKind::kRadialPad || 544 style == StyleKind::kConic || 545 style == StyleKind::kPatternNN || 546 style == StyleKind::kPatternBI ; 547 } 548 549 void BenchApp::serialize_system_info(JSONBuilder& json) const { 550 BLRuntimeSystemInfo system_info; 551 BLRuntime::query_system_info(&system_info); 552 553 json.before_record().add_key("environment").open_object(); 554 json.before_record().add_key("os").add_string(get_os_string()); 555 json.close_object(true); 556 557 json.before_record().add_key("cpu").open_object(); 558 json.before_record().add_key("arch").add_string(get_cpu_arch_string()); 559 json.before_record().add_key("vendor").add_string(system_info.cpu_vendor); 560 json.before_record().add_key("brand").add_string(system_info.cpu_brand); 561 json.close_object(true); 562 } 563 564 void BenchApp::serialize_params(JSONBuilder& json, const BenchParams& params) const { 565 json.before_record().add_key("screen").open_object(); 566 json.before_record().add_key("width").add_uint(params.screen_w); 567 json.before_record().add_key("height").add_uint(params.screen_h); 568 json.before_record().add_key("format").add_string(get_format_string(params.format)); 569 json.close_object(true); 570 } 571 572 void BenchApp::serialize_options(JSONBuilder& json, const BenchParams& params) const { 573 json.before_record().add_key("options").open_object(); 574 json.before_record().add_key("quantity").add_uint(params.quantity); 575 json.before_record().add_key("sizes").open_array(); 576 for (uint32_t size_index = 0; size_index < _size_count; size_index++) { 577 json.add_stringf("%ux%u", bench_shape_size_table[size_index], bench_shape_size_table[size_index]); 578 } 579 json.close_array(); 580 json.before_record().add_key("repeat").add_uint(_repeat); 581 json.close_object(true); 582 } 583 584 int BenchApp::run() { 585 BenchParams params{}; 586 params.screen_w = _width; 587 params.screen_h = _height; 588 params.format = BL_FORMAT_PRGB32; 589 params.stroke_width = 2.0; 590 591 BLString json_content; 592 JSONBuilder json(&json_content); 593 594 json.open_object(); 595 596 serialize_system_info(json); 597 serialize_params(json, params); 598 serialize_options(json, params); 599 600 json.before_record().add_key("runs").open_array(); 601 602 if (_isolated) { 603 BLRuntimeSystemInfo si; 604 BLRuntime::query_system_info(&si); 605 606 // Only use features that could actually make a difference. 607 static const uint32_t x86_features[] = { 608 BL_RUNTIME_CPU_FEATURE_X86_SSE2, 609 BL_RUNTIME_CPU_FEATURE_X86_SSE3, 610 BL_RUNTIME_CPU_FEATURE_X86_SSSE3, 611 BL_RUNTIME_CPU_FEATURE_X86_SSE4_1, 612 BL_RUNTIME_CPU_FEATURE_X86_SSE4_2, 613 BL_RUNTIME_CPU_FEATURE_X86_AVX, 614 BL_RUNTIME_CPU_FEATURE_X86_AVX2, 615 BL_RUNTIME_CPU_FEATURE_X86_AVX512 616 }; 617 618 const uint32_t* features = x86_features; 619 uint32_t feature_count = ARRAY_SIZE(x86_features); 620 621 for (uint32_t i = 0; i < feature_count; i++) { 622 if ((si.cpu_features & features[i]) == features[i]) { 623 Backend* backend = create_blend2d_backend(0, features[i]); 624 run_backend_tests(*backend, params, json); 625 delete backend; 626 } 627 } 628 } 629 else { 630 if (is_backend_enabled(BackendKind::kBlend2D)) { 631 Backend* backend = create_blend2d_backend(0); 632 run_backend_tests(*backend, params, json); 633 delete backend; 634 635 backend = create_blend2d_backend(2); 636 run_backend_tests(*backend, params, json); 637 delete backend; 638 639 backend = create_blend2d_backend(4); 640 run_backend_tests(*backend, params, json); 641 delete backend; 642 } 643 644 #if defined(BL_BENCH_ENABLE_AGG) 645 if (is_backend_enabled(BackendKind::kAGG)) { 646 Backend* backend = create_agg_backend(); 647 run_backend_tests(*backend, params, json); 648 delete backend; 649 } 650 #endif 651 652 #if defined(BL_BENCH_ENABLE_CAIRO) 653 if (is_backend_enabled(BackendKind::kCairo)) { 654 Backend* backend = create_cairo_backend(); 655 run_backend_tests(*backend, params, json); 656 delete backend; 657 } 658 #endif 659 660 #if defined(BL_BENCH_ENABLE_QT) 661 if (is_backend_enabled(BackendKind::kQt)) { 662 Backend* backend = create_qt_backend(); 663 run_backend_tests(*backend, params, json); 664 delete backend; 665 } 666 #endif 667 668 #if defined(BL_BENCH_ENABLE_SKIA) 669 if (is_backend_enabled(BackendKind::kSkia)) { 670 Backend* backend = create_skia_backend(); 671 run_backend_tests(*backend, params, json); 672 delete backend; 673 } 674 #endif 675 676 #if defined(BL_BENCH_ENABLE_JUCE) 677 if (is_backend_enabled(BackendKind::kJUCE)) { 678 Backend* backend = create_juce_backend(); 679 run_backend_tests(*backend, params, json); 680 delete backend; 681 } 682 #endif 683 684 #if defined(BL_BENCH_ENABLE_COREGRAPHICS) 685 if (is_backend_enabled(BackendKind::kCoreGraphics)) { 686 Backend* backend = create_cg_backend(); 687 run_backend_tests(*backend, params, json); 688 delete backend; 689 } 690 #endif 691 } 692 693 json.close_array(true); 694 json.close_object(true); 695 json.nl(); 696 697 printf("\n"); 698 fputs(json_content.data(), stdout); 699 700 return 0; 701 } 702 703 int BenchApp::run_backend_tests(Backend& backend, BenchParams& params, JSONBuilder& json) { 704 char file_name[256]; 705 BLString style_string; 706 707 BLImage overview_image; 708 BLContext overview_ctx; 709 710 if (_save_overview) { 711 overview_image.create(int(1u + (_width + 1u) * _size_count), int(_height + 2u), BL_FORMAT_XRGB32); 712 overview_ctx.begin(overview_image); 713 } 714 715 double cpms[kBenchShapeSizeCount] {}; 716 double cpms_total[kBenchShapeSizeCount] {}; 717 DurationFormat fmt[kBenchShapeSizeCount] {}; 718 719 uint32_t comp_op_first = BL_COMP_OP_SRC_OVER; 720 uint32_t comp_op_last = BL_COMP_OP_SRC_COPY; 721 722 if (_comp_op != 0xFFFFFFFFu) { 723 comp_op_first = comp_op_last = _comp_op; 724 } 725 726 json.before_record().open_object(); 727 json.before_record().add_key("name").add_string(backend.name()); 728 backend.serialize_info(json); 729 json.before_record().add_key("records").open_array(); 730 731 for (uint32_t comp_op = comp_op_first; comp_op <= comp_op_last; comp_op++) { 732 params.comp_op = BLCompOp(comp_op); 733 if (!backend.supports_comp_op(params.comp_op)) 734 continue; 735 736 for (uint32_t style_index = 0; style_index < kStyleKindCount; style_index++) { 737 StyleKind style = StyleKind(style_index); 738 if (!is_style_enabled(style) || !backend.supports_style(style)) { 739 continue; 740 } 741 742 params.style = style; 743 744 // Remove '@' from the style name if not running a deep benchmark. 745 style_string.assign(style_kind_name_table[style_index]); 746 747 if (!_deep_bench) { 748 size_t idx = style_string.index_of('@'); 749 if (idx != SIZE_MAX) { 750 style_string.truncate(idx); 751 } 752 } 753 754 memset(cpms_total, 0, sizeof(cpms_total)); 755 756 printf(bench_border_str); 757 printf(bench_header_str, backend._name); 758 printf(bench_border_str); 759 760 for (uint32_t test_index = 0; test_index < kTestKindCount; test_index++) { 761 params.testKind = TestKind(test_index); 762 763 if (_save_overview) { 764 overview_ctx.fill_all(BLRgba32(0xFF000000u)); 765 overview_ctx.stroke_rect(BLRect(0.5, 0.5, overview_image.width() - 1, overview_image.height() - 1), BLRgba32(0xFFFFFFFF)); 766 } 767 768 for (uint32_t size_index = 0; size_index < _size_count; size_index++) { 769 params.shape_size = bench_shape_size_table[size_index]; 770 uint64_t duration = run_single_test(backend, params); 771 772 cpms[size_index] = double(params.quantity) * double(1000) / double(duration); 773 cpms_total[size_index] += cpms[size_index]; 774 775 if (_save_overview) { 776 overview_ctx.blit_image( 777 BLPointI(int(1u + (size_index * (_width + 1u))), 1), 778 backend._surface); 779 780 overview_ctx.fill_rect( 781 BLRectI(int(1u + (size_index * (_width + 1u)) + _width), 1, 1, int(_height)), 782 BLRgba32(0xFFFFFFFF)); 783 784 if (size_index == _size_count - 1) { 785 snprintf(file_name, 256, "%s-%s-%s-%s.png", 786 backend._name, 787 test_kind_name_table[uint32_t(params.testKind)], 788 comp_op_name_table[uint32_t(params.comp_op)], 789 style_string.data()); 790 spacesToUnderscores(file_name); 791 overview_image.write_to_file(file_name); 792 } 793 } 794 795 if (_save_images) { 796 // Save only the last two as these are easier to compare visually. 797 if (size_index >= _size_count - 2) { 798 snprintf(file_name, 256, "%s-%s-%s-%s-%c.png", 799 backend._name, 800 test_kind_name_table[uint32_t(params.testKind)], 801 comp_op_name_table[uint32_t(params.comp_op)], 802 style_string.data(), 803 'A' + size_index); 804 spacesToUnderscores(file_name); 805 backend._surface.write_to_file(file_name); 806 } 807 } 808 } 809 810 for (uint32_t size_index = 0; size_index < _size_count; size_index++) { 811 fmt[size_index].format(cpms[size_index]); 812 } 813 814 printf(bench_fata_fmt_str, 815 test_kind_name_table[uint32_t(params.testKind)], 816 comp_op_name_table[uint32_t(params.comp_op)], 817 style_string.data(), 818 fmt[0].data, 819 fmt[1].data, 820 fmt[2].data, 821 fmt[3].data, 822 fmt[4].data, 823 fmt[5].data); 824 825 json.before_record() 826 .open_object() 827 .add_key("test").add_string(test_kind_name_table[uint32_t(params.testKind)]) 828 .comma().align_to(36).add_key("compOp").add_string(comp_op_name_table[uint32_t(params.comp_op)]) 829 .comma().align_to(58).add_key("style").add_string(style_string.data()); 830 831 json.add_key("rcpms").open_array(); 832 for (uint32_t size_index = 0; size_index < _size_count; size_index++) { 833 json.add_string_no_quotes(fmt[size_index].data); 834 } 835 json.close_array(); 836 837 json.close_object(); 838 } 839 840 for (uint32_t size_index = 0; size_index < _size_count; size_index++) { 841 fmt[size_index].format(cpms_total[size_index]); 842 } 843 844 printf(bench_border_str); 845 printf(bench_fata_fmt_str, 846 "Total", 847 comp_op_name_table[uint32_t(params.comp_op)], 848 style_string.data(), 849 fmt[0].data, 850 fmt[1].data, 851 fmt[2].data, 852 fmt[3].data, 853 fmt[4].data, 854 fmt[5].data); 855 printf(bench_border_str); 856 printf("\n"); 857 } 858 } 859 860 json.close_array(true); 861 json.close_object(true); 862 863 return 0; 864 } 865 866 uint64_t BenchApp::run_single_test(Backend& backend, BenchParams& params) { 867 constexpr uint32_t initial_quantity = 25; 868 constexpr uint32_t minimum_duration_in_us = 1000; 869 constexpr uint32_t max_repeat_if_no_improvement = 10; 870 871 uint32_t attempt = 0; 872 uint64_t duration = std::numeric_limits<uint64_t>::max(); 873 uint32_t no_improvement = 0; 874 875 params.quantity = _quantity; 876 877 if (_quantity == 0u) { 878 // If quantity is zero it means to deduce it based on execution time of each test. 879 params.quantity = initial_quantity; 880 for (;;) { 881 backend.run(*this, params); 882 if (backend._duration >= minimum_duration_in_us) { 883 // Make this the first attempt to reduce the time of benchmarking. 884 attempt = 1; 885 duration = backend._duration; 886 break; 887 } 888 889 if (backend._duration < 100) { 890 params.quantity *= 10; 891 } 892 else if (backend._duration < 500) { 893 params.quantity *= 3; 894 } 895 else { 896 params.quantity *= 2; 897 } 898 } 899 } 900 901 while (attempt < _repeat) { 902 backend.run(*this, params); 903 904 if (duration > backend._duration) { 905 duration = backend._duration; 906 } 907 else { 908 no_improvement++; 909 } 910 911 if (no_improvement >= max_repeat_if_no_improvement) 912 break; 913 914 attempt++; 915 } 916 917 return duration; 918 } 919 920 } // {blbench} 921 922 int main(int argc, char* argv[]) { 923 blbench::BenchApp app(argc, argv); 924 925 if (!app.init()) { 926 printf("Failed to initialize bl_bench.\n"); 927 return 1; 928 } 929 930 return app.run(); 931 }