odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

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 }