odin-blend2d

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

bl_test_context_utilities.h (35273B)


      1 // This file is part of Blend2D project <https://blend2d.com>
      2 //
      3 // See blend2d.h or LICENSE.md for license and copyright information
      4 // SPDX-License-Identifier: Zlib
      5 
      6 // This file provides utility classes and functions shared between some tests.
      7 
      8 #ifndef BLEND2D_TEST_CONTEXT_UTILITIES_H_INCLUDED
      9 #define BLEND2D_TEST_CONTEXT_UTILITIES_H_INCLUDED
     10 
     11 #include <blend2d.h>
     12 
     13 #include <math.h>
     14 #include <stdio.h>
     15 #include <stdlib.h>
     16 #include <string.h>
     17 
     18 #include <vector>
     19 
     20 namespace ContextTests {
     21 
     22 enum class CommandId : uint32_t {
     23   kFillRectI = 0,
     24   kFillRectD,
     25   kFillMultipleRects,
     26   kFillRound,
     27   kFillTriangle,
     28   kFillPoly10,
     29   kFillPathQuad,
     30   kFillPathCubic,
     31   kFillText,
     32   kStrokeRectI,
     33   kStrokeRectD,
     34   kStrokeMultipleRects,
     35   kStrokeRound,
     36   kStrokeTriangle,
     37   kStrokePoly10,
     38   kStrokePathQuad,
     39   kStrokePathCubic,
     40   kStrokeText,
     41   kAll,
     42 
     43   kMaxValue = kAll,
     44   kUnknown = 0xFFFFFFFFu
     45 };
     46 
     47 enum class CompOp : uint32_t {
     48   kSrcOver = BL_COMP_OP_SRC_OVER,
     49   kSrcCopy = BL_COMP_OP_SRC_COPY,
     50 
     51   kRandom,
     52   kAll,
     53 
     54   kMaxValue = kAll,
     55   kUnknown = 0xFFFFFFFFu
     56 };
     57 
     58 enum class OpacityOp : uint32_t {
     59   kOpaque,
     60   kSemi,
     61   kTransparent,
     62 
     63   kRandom,
     64   kAll,
     65 
     66   kMaxValue = kAll,
     67   kUnknown = 0xFFFFFFFFu
     68 };
     69 
     70 enum class StyleId : uint32_t {
     71   kSolid = 0,
     72   kSolidOpaque,
     73   kGradientLinear,
     74   kGradientLinearDither,
     75   kGradientRadial,
     76   kGradientRadialDither,
     77   kGradientConic,
     78   kGradientConicDither,
     79   kPatternAligned,
     80   kPatternFx,
     81   kPatternFy,
     82   kPatternFxFy,
     83   kPatternAffineNearest,
     84   kPatternAffineBilinear,
     85 
     86   kRandom,
     87   kRandomStable,
     88   kRandomUnstable,
     89 
     90   kAll,
     91   kAllStable,
     92   kAllUnstable,
     93 
     94   kMaxValue = kAllUnstable,
     95   kUnknown = 0xFFFFFFFFu
     96 };
     97 
     98 enum class StyleOp : uint32_t {
     99   kExplicit,
    100   kImplicit,
    101 
    102   kRandom,
    103   kAll,
    104 
    105   kMaxValue = kAll,
    106   kUnknown = 0xFFFFFFFFu
    107 };
    108 
    109 static inline bool is_random_style(StyleId style_id) noexcept {
    110   return style_id >= StyleId::kRandom && style_id <= StyleId::kRandomUnstable;
    111 }
    112 
    113 static inline uint32_t maximum_pixel_difference_of(StyleId style_id) noexcept {
    114   switch (style_id) {
    115     // These use FMA, thus Portable VS JIT implementation could differ.
    116     case StyleId::kGradientRadial:
    117     case StyleId::kGradientRadialDither:
    118     case StyleId::kGradientConic:
    119     case StyleId::kGradientConicDither:
    120     case StyleId::kRandom:
    121     case StyleId::kRandomUnstable:
    122       return 2;
    123 
    124     default:
    125       return 0;
    126   }
    127 }
    128 
    129 namespace StringUtils {
    130 
    131 [[maybe_unused]]
    132 static bool strieq(const char* a, const char* b) {
    133   size_t a_len = strlen(a);
    134   size_t b_len = strlen(b);
    135 
    136   if (a_len != b_len)
    137     return false;
    138 
    139   for (size_t i = 0; i < a_len; i++) {
    140     unsigned ac = (unsigned char)a[i];
    141     unsigned bc = (unsigned char)b[i];
    142 
    143     if (ac >= 'a' && ac <= 'z') ac -= 'a' - 'A';
    144     if (bc >= 'a' && bc <= 'z') bc -= 'a' - 'A';
    145 
    146     if (ac != bc)
    147       return false;
    148   }
    149 
    150   return true;
    151 }
    152 
    153 [[maybe_unused]]
    154 static const char* bool_to_string(bool value) {
    155   return value ? "true" : "false";
    156 }
    157 
    158 [[maybe_unused]]
    159 static const char* cpu_x86_feature_to_string(BLRuntimeCpuFeatures feature) {
    160   switch (feature) {
    161     case BL_RUNTIME_CPU_FEATURE_X86_SSE2    : return "sse2";
    162     case BL_RUNTIME_CPU_FEATURE_X86_SSE3    : return "sse3";
    163     case BL_RUNTIME_CPU_FEATURE_X86_SSSE3   : return "ssse3";
    164     case BL_RUNTIME_CPU_FEATURE_X86_SSE4_1  : return "sse4.1";
    165     case BL_RUNTIME_CPU_FEATURE_X86_SSE4_2  : return "sse4.2";
    166     case BL_RUNTIME_CPU_FEATURE_X86_AVX     : return "avx";
    167     case BL_RUNTIME_CPU_FEATURE_X86_AVX2    : return "avx2";
    168     case BL_RUNTIME_CPU_FEATURE_X86_AVX512  : return "avx512";
    169 
    170     default:
    171       return "unknown";
    172   }
    173 }
    174 
    175 [[maybe_unused]]
    176 static const char* format_to_string(BLFormat format) {
    177   switch (format) {
    178     case BL_FORMAT_NONE  : return "none";
    179     case BL_FORMAT_PRGB32: return "prgb32";
    180     case BL_FORMAT_XRGB32: return "xrgb32";
    181     case BL_FORMAT_A8    : return "a8";
    182 
    183     default:
    184       return "unknown";
    185   }
    186 }
    187 
    188 [[maybe_unused]]
    189 static const char* style_id_to_string(StyleId style_id) {
    190   switch (style_id) {
    191     case StyleId::kSolid                : return "solid";
    192     case StyleId::kSolidOpaque          : return "solid-opaque";
    193     case StyleId::kGradientLinear       : return "gradient-linear";
    194     case StyleId::kGradientLinearDither : return "gradient-linear-dither";
    195     case StyleId::kGradientRadial       : return "gradient-radial";
    196     case StyleId::kGradientRadialDither : return "gradient-radial-dither";
    197     case StyleId::kGradientConic        : return "gradient-conic";
    198     case StyleId::kGradientConicDither  : return "gradient-conic-dither";
    199     case StyleId::kPatternAligned       : return "pattern-aligned";
    200     case StyleId::kPatternFx            : return "pattern-fx";
    201     case StyleId::kPatternFy            : return "pattern-fy";
    202     case StyleId::kPatternFxFy          : return "pattern-fx-fy";
    203     case StyleId::kPatternAffineNearest : return "pattern-affine-nearest";
    204     case StyleId::kPatternAffineBilinear: return "pattern-affine-bilinear";
    205     case StyleId::kRandom               : return "random";
    206     case StyleId::kRandomStable         : return "random-stable";
    207     case StyleId::kRandomUnstable       : return "random-unstable";
    208     case StyleId::kAll                  : return "all";
    209     case StyleId::kAllStable            : return "all-stable";
    210     case StyleId::kAllUnstable          : return "all-unstable";
    211 
    212     default:
    213       return "unknown";
    214   }
    215 }
    216 
    217 [[maybe_unused]]
    218 static const char* style_op_to_string(StyleOp style_op) {
    219   switch (style_op) {
    220     case StyleOp::kExplicit             : return "explicit";
    221     case StyleOp::kImplicit             : return "implicit";
    222     case StyleOp::kRandom               : return "random";
    223 
    224     default:
    225       return "unknown";
    226   }
    227 }
    228 
    229 [[maybe_unused]]
    230 static const char* comp_op_to_string(CompOp comp_op) {
    231   switch (comp_op) {
    232     case CompOp::kSrcOver               : return "src-over";
    233     case CompOp::kSrcCopy               : return "src-copy";
    234     case CompOp::kRandom                : return "random";
    235     case CompOp::kAll                   : return "all";
    236 
    237     default:
    238       return "unknown";
    239   }
    240 }
    241 
    242 [[maybe_unused]]
    243 static const char* opacity_op_to_string(OpacityOp opacity) {
    244   switch (opacity) {
    245     case OpacityOp::kOpaque             : return "opaque";
    246     case OpacityOp::kSemi               : return "semi";
    247     case OpacityOp::kTransparent        : return "transparent";
    248     case OpacityOp::kRandom             : return "random";
    249     case OpacityOp::kAll                : return "all";
    250 
    251     default:
    252       return "unknown";
    253   }
    254 }
    255 
    256 [[maybe_unused]]
    257 static const char* command_id_to_string(CommandId command) {
    258   switch (command) {
    259     case CommandId::kFillRectI          : return "fill-rect-i";
    260     case CommandId::kFillRectD          : return "fill-rect-d";
    261     case CommandId::kFillMultipleRects  : return "fill-multiple-rects";
    262     case CommandId::kFillRound          : return "fill-round";
    263     case CommandId::kFillTriangle       : return "fill-triangle";
    264     case CommandId::kFillPoly10         : return "fill-poly-10";
    265     case CommandId::kFillPathQuad       : return "fill-path-quad";
    266     case CommandId::kFillPathCubic      : return "fill-path-cubic";
    267     case CommandId::kFillText           : return "fill-text";
    268     case CommandId::kStrokeRectI        : return "stroke-rect-i";
    269     case CommandId::kStrokeRectD        : return "stroke-rect-d";
    270     case CommandId::kStrokeMultipleRects: return "stroke-multiple-rects";
    271     case CommandId::kStrokeRound        : return "stroke-round";
    272     case CommandId::kStrokeTriangle     : return "stroke-triangle";
    273     case CommandId::kStrokePoly10       : return "stroke-poly-10";
    274     case CommandId::kStrokePathQuad     : return "stroke-path-quad";
    275     case CommandId::kStrokePathCubic    : return "stroke-path-cubic";
    276     case CommandId::kStrokeText         : return "stroke-text";
    277     case CommandId::kAll                : return "all";
    278 
    279     default:
    280       return "unknown";
    281   }
    282 }
    283 
    284 [[maybe_unused]]
    285 static BLFormat parse_format(const char* s) {
    286   for (uint32_t i = 0; i <= uint32_t(BL_FORMAT_MAX_VALUE); i++)
    287     if (strieq(s, format_to_string(BLFormat(i))))
    288       return BLFormat(i);
    289   return BL_FORMAT_NONE;
    290 }
    291 
    292 [[maybe_unused]]
    293 static StyleId parse_style_id(const char* s) {
    294   for (uint32_t i = 0; i <= uint32_t(StyleId::kMaxValue); i++)
    295     if (strieq(s, style_id_to_string(StyleId(i))))
    296       return StyleId(i);
    297   return StyleId::kUnknown;
    298 }
    299 
    300 [[maybe_unused]]
    301 static StyleOp parse_style_op(const char* s) {
    302   for (uint32_t i = 0; i <= uint32_t(StyleOp::kMaxValue); i++)
    303     if (strieq(s, style_op_to_string(StyleOp(i))))
    304       return StyleOp(i);
    305   return StyleOp::kUnknown;
    306 }
    307 
    308 [[maybe_unused]]
    309 static CompOp parse_comp_op(const char* s) {
    310   for (uint32_t i = 0; i <= uint32_t(CompOp::kMaxValue); i++)
    311     if (strieq(s, comp_op_to_string(CompOp(i))))
    312       return CompOp(i);
    313   return CompOp::kUnknown;
    314 }
    315 
    316 [[maybe_unused]]
    317 static OpacityOp parse_opacity_op(const char* s) {
    318   for (uint32_t i = 0; i <= uint32_t(OpacityOp::kMaxValue); i++)
    319     if (strieq(s, opacity_op_to_string(OpacityOp(i))))
    320       return OpacityOp(i);
    321   return OpacityOp::kUnknown;
    322 }
    323 
    324 [[maybe_unused]]
    325 static CommandId parse_command_id(const char* s) {
    326   for (uint32_t i = 0; i <= uint32_t(CommandId::kMaxValue); i++)
    327     if (strieq(s, command_id_to_string(CommandId(i))))
    328       return CommandId(i);
    329   return CommandId::kUnknown;
    330 }
    331 
    332 } // {StringUtils}
    333 
    334 class Logger {
    335 public:
    336   enum class Verbosity : uint32_t {
    337     Debug,
    338     Info,
    339     Silent
    340   };
    341 
    342   Verbosity _verbosity;
    343 
    344   inline Logger(Verbosity verbosity)
    345     : _verbosity(verbosity) {}
    346 
    347   inline Verbosity verbosity() const { return _verbosity; }
    348 
    349   inline Verbosity set_verbosity(Verbosity value) {
    350     Verbosity prev = _verbosity;
    351     _verbosity = value;
    352     return prev;
    353   }
    354 
    355   inline void print(const char* fmt) {
    356     puts(fmt);
    357     fflush(stdout);
    358   }
    359 
    360   template<typename... Args>
    361   inline void print(const char* fmt, Args&&... args) {
    362     printf(fmt, BLInternal::forward<Args>(args)...);
    363     fflush(stdout);
    364   }
    365 
    366   template<typename... Args>
    367   inline void debug(const char* fmt, Args&&... args) {
    368     if (_verbosity <= Verbosity::Debug)
    369       print(fmt, BLInternal::forward<Args>(args)...);
    370   }
    371 
    372   template<typename... Args>
    373   inline void info(const char* fmt, Args&&... args) {
    374     if (_verbosity <= Verbosity::Info)
    375       print(fmt, BLInternal::forward<Args>(args)...);
    376   }
    377 };
    378 
    379 struct TestCases {
    380   //! List of pixel formats to test.
    381   std::vector<BLFormat> format_ids;
    382   //! List of commands to test.
    383   std::vector<CommandId> command_ids;
    384   //! List of styles test.
    385   std::vector<StyleId> style_ids;
    386   //! List of styles operations to test (implicit, explicit, random).
    387   std::vector<StyleOp> style_ops;
    388   //! List of composition operators to test (or that should be randomized in random case).
    389   std::vector<CompOp> comp_ops;
    390   //! List of opacity operators to test (or that should be randomized in random case).
    391   std::vector<OpacityOp> opacity_ops;
    392 };
    393 
    394 struct TestOptions {
    395   uint32_t width {};
    396   uint32_t height {};
    397   BLFormat format {};
    398   uint32_t count {};
    399   uint32_t thread_count {};
    400   uint32_t seed {};
    401   CompOp comp_op = CompOp::kSrcOver;
    402   OpacityOp opacity_op = OpacityOp::kOpaque;
    403   StyleId style_id = StyleId::kSolid;
    404   StyleOp style_op = StyleOp::kRandom;
    405   CommandId command = CommandId::kAll;
    406   const char* font {};
    407   uint32_t font_size {};
    408   uint32_t face_index {};
    409 
    410   bool quiet {};
    411   bool flush_sync {};
    412   bool store_images {};
    413 };
    414 
    415 class RandomDataGenerator {
    416 public:
    417   enum class Mode : uint32_t {
    418     InBounds = 0
    419   };
    420 
    421   BLRandom _rnd;
    422   Mode _mode;
    423   BLBox _bounds;
    424   BLSize _size;
    425 
    426   RandomDataGenerator()
    427     : _rnd(0x123456789ABCDEFu),
    428       _mode(Mode::InBounds),
    429       _bounds(),
    430       _size() {}
    431 
    432   inline Mode mode() const { return _mode; }
    433   inline void set_mode(Mode mode) { _mode = mode; }
    434 
    435   inline const BLBox& bounds() const { return _bounds; }
    436   inline void set_bounds(const BLBox& bounds) {
    437     _bounds = bounds;
    438     _size.reset(_bounds.x1 - _bounds.x0, _bounds.y1 - _bounds.y0);
    439   }
    440 
    441   inline void seed(uint64_t value) { _rnd.reset(value); }
    442 
    443   inline CompOp next_comp_op() { return CompOp(_rnd.next_uint32() % uint32_t(CompOp::kRandom)); }
    444   inline BLExtendMode next_pattern_extend() { return BLExtendMode(_rnd.next_uint32() % (BL_EXTEND_MODE_MAX_VALUE + 1u)); }
    445   inline BLExtendMode next_gradient_extend() { return BLExtendMode(_rnd.next_uint32() % (BL_EXTEND_MODE_SIMPLE_MAX_VALUE + 1u)); }
    446 
    447   inline uint32_t next_uint32() { return _rnd.next_uint32(); }
    448   inline uint64_t next_uint64() { return _rnd.next_uint64(); }
    449   inline double next_double() { return _rnd.next_double(); }
    450 
    451   inline BLRgba32 next_rgb32() { return BLRgba32(_rnd.next_uint32() | 0xFF000000u); }
    452   inline BLRgba32 next_rgba32() { return BLRgba32(_rnd.next_uint32()); }
    453 
    454   inline int next_x_coord_i() { return int((_rnd.next_double() * _size.w) + _bounds.x0); }
    455   inline int next_y_coord_i() { return int((_rnd.next_double() * _size.h) + _bounds.y0); }
    456 
    457   inline double next_x_coord_d() { return (_rnd.next_double() * _size.w) + _bounds.x0; }
    458   inline double next_y_coord_d() { return (_rnd.next_double() * _size.h) + _bounds.y0; }
    459 
    460   inline BLPoint next_point_d() { return BLPoint(next_x_coord_d(), next_y_coord_d()); }
    461   inline BLPointI next_point_i() { return BLPointI(next_x_coord_i(), next_y_coord_i()); }
    462 
    463   inline BLBox next_box_d() {
    464     double x0 = next_x_coord_d();
    465     double y0 = next_y_coord_d();
    466     double x1 = next_x_coord_d();
    467     double y1 = next_y_coord_d();
    468     return BLBox(bl_min(x0, x1), bl_min(y0, y1), bl_max(x0, x1), bl_max(y0, y1));
    469   }
    470 
    471   inline BLBoxI next_box_i() {
    472     int x0 = next_x_coord_i();
    473     int y0 = next_y_coord_i();
    474     int x1 = next_x_coord_i();
    475     int y1 = next_y_coord_i();
    476 
    477     if (x0 > x1) BLInternal::swap(x0, x1);
    478     if (y0 > y1) BLInternal::swap(y0, y1);
    479 
    480     if (x0 == x1) x1++;
    481     if (y0 == y1) y1++;
    482 
    483     return BLBoxI(x0, y0, x1, y1);
    484   }
    485 
    486   inline BLRectI next_rect_i() {
    487     BLBoxI box = next_box_i();
    488     return BLRectI(box.x0, box.y0, box.x1 - box.x0, box.y1 - box.y0);
    489   }
    490 
    491   inline BLRect next_rect_d() {
    492     BLBox box = next_box_d();
    493     return BLRect(box.x0, box.y0, box.x1 - box.x0, box.y1 - box.y0);
    494   }
    495 
    496   inline BLTriangle next_triangle() {
    497     BLTriangle out;
    498     out.x0 = next_x_coord_d();
    499     out.y0 = next_y_coord_d();
    500     out.x1 = next_x_coord_d();
    501     out.y1 = next_y_coord_d();
    502     out.x2 = next_x_coord_d();
    503     out.y2 = next_y_coord_d();
    504     return out;
    505   }
    506 };
    507 
    508 class ContextTester {
    509 public:
    510   static inline constexpr uint32_t kTextureCount = 8;
    511 
    512   enum class Op { kFill, kStroke };
    513 
    514   const TestCases& _test_cases;
    515 
    516   RandomDataGenerator _rnd;
    517   BLRandom _rnd_sync;
    518   BLRandom _rnd_comp_op;
    519   BLRandom _rnd_opacity_op;
    520   BLRandom _rnd_opacity_value;
    521   BLRandom _rnd_style_op;
    522   const char* _prefix {};
    523   BLImage _img;
    524   BLContext _ctx;
    525   CompOp _comp_op {};
    526   OpacityOp _opacity_op {};
    527   StyleId _style_id {};
    528   StyleOp _style_op {};
    529   bool _flush_sync {};
    530 
    531   BLImage _textures[kTextureCount];
    532   BLFontData _font_data;
    533 
    534   ContextTester(const TestCases& test_cases, const char* prefix)
    535     : _test_cases(test_cases),
    536       _rnd_sync(0u),
    537       _prefix(prefix),
    538       _flush_sync(false) {}
    539 
    540   BLResult init(int w, int h, BLFormat format, const BLContextCreateInfo& cci) {
    541     BL_PROPAGATE(_img.create(w, h, format));
    542     BL_PROPAGATE(_ctx.begin(_img, cci));
    543 
    544     double oob = 30;
    545 
    546     _rnd.set_bounds(BLBox(0.0 - oob, 0.0 - oob, w + oob, h + oob));
    547     _ctx.clear_all();
    548     _ctx.set_fill_style(BLRgba32(0xFFFFFFFF));
    549 
    550     for (uint32_t i = 0; i < kTextureCount; i++) {
    551       BL_PROPAGATE(init_texture(i));
    552     }
    553 
    554     return BL_SUCCESS;
    555   }
    556 
    557   BLResult init_texture(uint32_t id) noexcept {
    558     static constexpr int sizes[kTextureCount] = {
    559       17,
    560       19,
    561       47,
    562       63,
    563       121,
    564       345,
    565       417,
    566       512
    567     };
    568 
    569     static constexpr BLFormat formats[kTextureCount] = {
    570       BL_FORMAT_PRGB32,
    571       BL_FORMAT_A8,
    572       BL_FORMAT_PRGB32,
    573       BL_FORMAT_PRGB32,
    574       BL_FORMAT_PRGB32,
    575       BL_FORMAT_A8,
    576       BL_FORMAT_PRGB32,
    577       BL_FORMAT_PRGB32
    578     };
    579 
    580     int size = sizes[id];
    581     BLFormat format = formats[id];
    582 
    583     BL_PROPAGATE(_textures[id].create(size, size, format));
    584 
    585     // Disable JIT here as we may be testing it in the future. If there is
    586     // a bug in JIT we want to find it by tests, and not to face it here...
    587     BLContextCreateInfo cci {};
    588     cci.flags = BL_CONTEXT_CREATE_FLAG_DISABLE_JIT;
    589 
    590     BLContext ctx;
    591 
    592     BL_PROPAGATE(ctx.begin(_textures[id], cci));
    593     ctx.clear_all();
    594 
    595     double s = double(size);
    596     double half = s * 0.5;
    597 
    598     ctx.fill_circle(half, half, half * 1.00, BLRgba32(0xFFFFFFFF));
    599     ctx.fill_circle(half + half * 0.33, half, half * 0.66, BLRgba32(0xFFFF0000));
    600     ctx.fill_circle(half, half, half * 0.33, BLRgba32(0xFF0000FF));
    601 
    602     return BL_SUCCESS;
    603   }
    604 
    605   inline void seed(uint32_t seed) { _rnd.seed(seed); }
    606   inline void set_options(CompOp comp_op, OpacityOp opacity_op, StyleId style_id, StyleOp style_op) {
    607     _comp_op = comp_op;
    608     _opacity_op = opacity_op;
    609     _style_id = style_id;
    610     _style_op = style_op;
    611   }
    612 
    613   inline void set_font_data(const BLFontData& font_data) { _font_data = font_data; }
    614   inline void set_flush_sync(bool value) { _flush_sync = value; }
    615 
    616   const char* prefix() const { return _prefix; }
    617   inline const BLImage& image() const { return _img; }
    618 
    619   void reset() {
    620     _ctx.reset();
    621     _img.reset();
    622   }
    623 
    624   void started([[maybe_unused]] const char* test_name) {
    625     _rnd_sync.reset(0xA29CF911A3B729AFu);
    626     _rnd_comp_op.reset(0xBF4D32C15432343Fu);
    627     _rnd_opacity_op.reset(0xFA4DF28C54880133u);
    628     _rnd_opacity_value.reset(0xF987FCABB3434DDDu);
    629     _rnd_style_op.reset(0x23BF4E98B4F3AABDu);
    630   }
    631 
    632   void finished([[maybe_unused]] const char* test_name) {
    633     _ctx.flush(BL_CONTEXT_FLUSH_SYNC);
    634   }
    635 
    636   inline void record_iteration([[maybe_unused]] size_t n) {
    637     if (_flush_sync && _rnd_sync.next_uint32() > 0xF0000000u) {
    638       _ctx.flush(BL_CONTEXT_FLUSH_SYNC);
    639     }
    640   }
    641 
    642   inline StyleId next_style_id() {
    643     StyleId style_id = _style_id;
    644     if (is_random_style(style_id)) {
    645       style_id = _test_cases.style_ids[_rnd.next_uint32() % _test_cases.style_ids.size()];
    646     }
    647     return style_id;
    648   }
    649 
    650   inline StyleOp next_style_op() {
    651     if (_style_op == StyleOp::kRandom)
    652       return _test_cases.style_ops[_rnd_style_op.next_uint32() % _test_cases.style_ops.size()];
    653     else
    654       return _style_op;
    655   }
    656 
    657   void setup_common_options(BLContext& ctx) {
    658     if (_comp_op == CompOp::kRandom) {
    659       ctx.set_comp_op(BLCompOp(_test_cases.comp_ops[_rnd_comp_op.next_uint32() % _test_cases.comp_ops.size()]));
    660     }
    661 
    662     if (_opacity_op == OpacityOp::kRandom || _opacity_op == OpacityOp::kSemi) {
    663       OpacityOp op = _opacity_op;
    664       if (op == OpacityOp::kRandom) {
    665         op = _test_cases.opacity_ops[_rnd_opacity_op.next_uint32() % _test_cases.opacity_ops.size()];
    666       }
    667 
    668       double alpha = 0.0;
    669       switch (op) {
    670         case OpacityOp::kOpaque     : alpha = 1.0; break;
    671         case OpacityOp::kSemi       : alpha = _rnd_opacity_value.next_double(); break;
    672         case OpacityOp::kTransparent: alpha = 0.0; break;
    673         default:
    674           break;
    675       }
    676 
    677       _ctx.set_global_alpha(alpha);
    678     }
    679   }
    680 
    681   void setup_style_options(BLContext& ctx, StyleId style_id) {
    682     switch (style_id) {
    683       case StyleId::kGradientLinear:
    684       case StyleId::kGradientRadial:
    685       case StyleId::kGradientConic:
    686         ctx.set_gradient_quality(BL_GRADIENT_QUALITY_NEAREST);
    687         break;
    688 
    689       case StyleId::kGradientLinearDither:
    690       case StyleId::kGradientRadialDither:
    691       case StyleId::kGradientConicDither:
    692         ctx.set_gradient_quality(BL_GRADIENT_QUALITY_DITHER);
    693         break;
    694 
    695       case StyleId::kPatternAligned:
    696       case StyleId::kPatternAffineNearest:
    697         ctx.set_pattern_quality(BL_PATTERN_QUALITY_NEAREST);
    698         break;
    699 
    700       case StyleId::kPatternFx:
    701       case StyleId::kPatternFy:
    702       case StyleId::kPatternFxFy:
    703       case StyleId::kPatternAffineBilinear:
    704         ctx.set_pattern_quality(BL_PATTERN_QUALITY_BILINEAR);
    705         break;
    706 
    707       default:
    708         break;
    709     }
    710   }
    711 
    712   BLVar materialize_style(StyleId style_id) {
    713     static constexpr double kPI = 3.14159265358979323846;
    714 
    715     switch (style_id) {
    716       default:
    717       case StyleId::kSolid: {
    718         return BLVar(_rnd.next_rgba32());
    719       }
    720 
    721       case StyleId::kSolidOpaque: {
    722         return BLVar(_rnd.next_rgb32());
    723       }
    724 
    725       case StyleId::kGradientLinear:
    726       case StyleId::kGradientLinearDither: {
    727         BLPoint pt0 = _rnd.next_point_d();
    728         BLPoint pt1 = _rnd.next_point_d();
    729 
    730         BLGradient gradient(BLLinearGradientValues(pt0.x, pt0.y, pt1.x, pt1.y));
    731         gradient.add_stop(0.0, _rnd.next_rgba32());
    732         gradient.add_stop(0.5, _rnd.next_rgba32());
    733         gradient.add_stop(1.0, _rnd.next_rgba32());
    734         gradient.set_extend_mode(_rnd.next_gradient_extend());
    735         return BLVar(BLInternal::move(gradient));
    736       }
    737 
    738       case StyleId::kGradientRadial:
    739       case StyleId::kGradientRadialDither: {
    740         // NOTE: It's tricky with radial gradients as FMA and non-FMA implementations will have a different output.
    741         // So, we quantize input coordinates to integers to minimize the damage, although we cannot avoid it even
    742         // in this case.
    743         double rad = floor(_rnd.next_double() * 500 + 20);
    744         double dist = floor(_rnd.next_double() * (rad - 10));
    745 
    746         double angle = _rnd.next_double() * kPI;
    747         double as = sin(angle);
    748         double ac = cos(angle);
    749 
    750         BLPoint pt0 = _rnd.next_point_i();
    751         BLPoint pt1 = BLPoint(floor(-as * dist), floor(ac * dist)) + pt0;
    752 
    753         BLGradient gradient(BLRadialGradientValues(pt0.x, pt0.y, pt1.x, pt1.y, rad));
    754         BLRgba32 c = _rnd.next_rgba32();
    755         gradient.add_stop(0.0, c);
    756         gradient.add_stop(0.5, _rnd.next_rgba32());
    757         gradient.add_stop(1.0, c);
    758         gradient.set_extend_mode(_rnd.next_gradient_extend());
    759         return BLVar(BLInternal::move(gradient));
    760       }
    761 
    762       case StyleId::kGradientConic:
    763       case StyleId::kGradientConicDither: {
    764         BLPoint pt0 = _rnd.next_point_i();
    765         double angle = _rnd.next_double() * kPI;
    766 
    767         BLGradient gradient(BLConicGradientValues(pt0.x, pt0.y, angle));
    768         gradient.add_stop(0.0 , _rnd.next_rgba32());
    769         gradient.add_stop(0.33, _rnd.next_rgba32());
    770         gradient.add_stop(0.66, _rnd.next_rgba32());
    771         gradient.add_stop(1.0 , _rnd.next_rgba32());
    772         return BLVar(BLInternal::move(gradient));
    773       }
    774 
    775       case StyleId::kPatternAligned:
    776       case StyleId::kPatternFx:
    777       case StyleId::kPatternFy:
    778       case StyleId::kPatternFxFy: {
    779         static constexpr double kFracMin = 0.004;
    780         static constexpr double kFracMax = 0.994;
    781 
    782         uint32_t texture_id = _rnd.next_uint32() % kTextureCount;
    783         BLExtendMode extend_mode = BLExtendMode(_rnd.next_uint32() % (BL_EXTEND_MODE_MAX_VALUE + 1));
    784 
    785         BLPattern pattern(_textures[texture_id], extend_mode);
    786         pattern.translate(floor(_rnd.next_double() * double(_rnd._size.w + 200) - 100.0),
    787                           floor(_rnd.next_double() * double(_rnd._size.h + 200) - 100.0));
    788 
    789         if (style_id == StyleId::kPatternFx || style_id == StyleId::kPatternFxFy) {
    790           pattern.translate(bl_clamp(_rnd.next_double(), kFracMin, kFracMax), 0.0);
    791         }
    792 
    793         if (style_id == StyleId::kPatternFy || style_id == StyleId::kPatternFxFy) {
    794           pattern.translate(0.0, bl_clamp(_rnd.next_double(), kFracMin, kFracMax));
    795         }
    796 
    797         return BLVar(BLInternal::move(pattern));
    798       }
    799 
    800       case StyleId::kPatternAffineNearest:
    801       case StyleId::kPatternAffineBilinear: {
    802         uint32_t texture_id = _rnd.next_uint32() % kTextureCount;
    803         BLExtendMode extend_mode = BLExtendMode(_rnd.next_uint32() % (BL_EXTEND_MODE_MAX_VALUE + 1));
    804 
    805         BLPattern pattern(_textures[texture_id]);
    806         pattern.set_extend_mode(extend_mode);
    807         pattern.rotate(_rnd.next_double() * (kPI * 2.0));
    808         pattern.translate(_rnd.next_double() * 300, _rnd.next_double() * 300);
    809         pattern.scale((_rnd.next_double() + 0.2) * 2.4);
    810         return BLVar(BLInternal::move(pattern));
    811       }
    812     }
    813   }
    814 
    815   void clear() { _ctx.clear_all(); }
    816 
    817   void render(CommandId command_id, size_t n, const TestOptions& options) {
    818     const char* test_name = StringUtils::command_id_to_string(command_id);
    819     started(test_name);
    820 
    821     if (_comp_op != CompOp::kRandom) {
    822       _ctx.set_comp_op(BLCompOp(_comp_op));
    823     }
    824 
    825     if (_opacity_op != OpacityOp::kRandom) {
    826       _ctx.set_global_alpha(_opacity_op == OpacityOp::kOpaque ? 1.0 : 0.0);
    827     }
    828 
    829     switch (command_id) {
    830       case CommandId::kFillRectI:
    831         render_rect_i<Op::kFill>(n);
    832         break;
    833 
    834       case CommandId::kFillRectD:
    835         render_rect_d<Op::kFill>(n);
    836         break;
    837 
    838       case CommandId::kFillMultipleRects:
    839         render_multiple_rects<Op::kFill>(n);
    840         break;
    841 
    842       case CommandId::kFillRound:
    843         render_rounded_rect<Op::kFill>(n);
    844         break;
    845 
    846       case CommandId::kFillTriangle:
    847         render_triangle<Op::kFill>(n);
    848         break;
    849 
    850       case CommandId::kFillPoly10:
    851         render_poly_10<Op::kFill>(n);
    852         break;
    853 
    854       case CommandId::kFillPathQuad:
    855         render_path_quads<Op::kFill>(n);
    856         break;
    857 
    858       case CommandId::kFillPathCubic:
    859         render_path_cubics<Op::kFill>(n);
    860         break;
    861 
    862       case CommandId::kFillText:
    863         render_text<Op::kFill>(n, options.face_index, float(int(options.font_size)));
    864         break;
    865 
    866       case CommandId::kStrokeRectI:
    867         render_rect_i<Op::kStroke>(n);
    868         break;
    869 
    870       case CommandId::kStrokeRectD:
    871         render_rect_d<Op::kStroke>(n);
    872         break;
    873 
    874       case CommandId::kStrokeMultipleRects:
    875         render_multiple_rects<Op::kStroke>(n);
    876         break;
    877 
    878       case CommandId::kStrokeRound:
    879         render_rounded_rect<Op::kStroke>(n);
    880         break;
    881 
    882       case CommandId::kStrokeTriangle:
    883         render_triangle<Op::kStroke>(n);
    884         break;
    885 
    886       case CommandId::kStrokePoly10:
    887         render_poly_10<Op::kStroke>(n);
    888         break;
    889 
    890       case CommandId::kStrokePathQuad:
    891         render_path_quads<Op::kStroke>(n);
    892         break;
    893 
    894       case CommandId::kStrokePathCubic:
    895         render_path_cubics<Op::kStroke>(n);
    896         break;
    897 
    898       case CommandId::kStrokeText:
    899         render_text<Op::kStroke>(n, options.face_index, float(int(options.font_size)));
    900         break;
    901 
    902       default:
    903         break;
    904     }
    905 
    906     finished(test_name);
    907   }
    908 
    909   template<Op kOp>
    910   void render_path(const BLPath& path, StyleId style_id) {
    911     BLVar style = materialize_style(style_id);
    912 
    913     if (next_style_op() == StyleOp::kExplicit) {
    914       if constexpr (kOp == Op::kFill) {
    915         _ctx.fill_path(path, style);
    916       }
    917       else {
    918         _ctx.stroke_path(path, style);
    919       }
    920     }
    921     else {
    922       if constexpr (kOp == Op::kFill) {
    923         _ctx.set_fill_style(style);
    924         _ctx.fill_path(path);
    925       }
    926       else {
    927         _ctx.set_stroke_style(style);
    928         _ctx.stroke_path(path);
    929       }
    930     }
    931   }
    932   template<Op kOp>
    933   void render_rect_i(size_t n) {
    934     for (size_t i = 0; i < n; i++) {
    935       StyleId style_id = next_style_id();
    936 
    937       setup_common_options(_ctx);
    938       setup_style_options(_ctx, style_id);
    939 
    940       BLRectI rect = _rnd.next_rect_i();
    941       BLVar style = materialize_style(style_id);
    942 
    943       if (next_style_op() == StyleOp::kExplicit) {
    944         if constexpr (kOp == Op::kFill) {
    945           _ctx.fill_rect(rect, style);
    946         }
    947         else {
    948           _ctx.stroke_rect(rect, style);
    949         }
    950       }
    951       else {
    952         if constexpr (kOp == Op::kFill) {
    953           _ctx.set_fill_style(style);
    954           _ctx.fill_rect(rect);
    955         }
    956         else {
    957           _ctx.set_stroke_style(style);
    958           _ctx.stroke_rect(rect);
    959         }
    960       }
    961       record_iteration(i);
    962     }
    963   }
    964 
    965   template<Op kOp>
    966   void render_rect_d(size_t n) {
    967     for (size_t i = 0; i < n; i++) {
    968       StyleId style_id = next_style_id();
    969 
    970       setup_common_options(_ctx);
    971       setup_style_options(_ctx, style_id);
    972 
    973       BLRect rect = _rnd.next_rect_d();
    974       BLVar style = materialize_style(style_id);
    975 
    976       if (next_style_op() == StyleOp::kExplicit) {
    977         if constexpr (kOp == Op::kFill) {
    978           _ctx.fill_rect(rect, style);
    979         }
    980         else {
    981           _ctx.stroke_rect(rect, style);
    982         }
    983       }
    984       else {
    985         if constexpr (kOp == Op::kFill) {
    986           _ctx.set_fill_style(style);
    987           _ctx.fill_rect(rect, style);
    988         }
    989         else {
    990           _ctx.set_stroke_style(style);
    991           _ctx.stroke_rect(rect, style);
    992         }
    993       }
    994 
    995       record_iteration(i);
    996     }
    997   }
    998 
    999   template<Op kOp>
   1000   void render_multiple_rects(size_t n) {
   1001     for (size_t i = 0; i < n; i++) {
   1002       StyleId style_id = next_style_id();
   1003 
   1004       setup_common_options(_ctx);
   1005       setup_style_options(_ctx, style_id);
   1006 
   1007       BLPath path;
   1008       path.add_rect(_rnd.next_rect_d());
   1009       path.add_rect(_rnd.next_rect_d());
   1010 
   1011       render_path<kOp>(path, style_id);
   1012       record_iteration(i);
   1013     }
   1014   }
   1015 
   1016   template<Op kOp>
   1017   void render_rounded_rect(size_t n) {
   1018     for (size_t i = 0; i < n; i++) {
   1019       StyleId style_id = next_style_id();
   1020 
   1021       setup_common_options(_ctx);
   1022       setup_style_options(_ctx, style_id);
   1023 
   1024       BLRect rect = _rnd.next_rect_d();
   1025       BLPoint r = _rnd.next_point_d();
   1026 
   1027       BLVar style = materialize_style(style_id);
   1028 
   1029       if (next_style_op() == StyleOp::kExplicit) {
   1030         if constexpr (kOp == Op::kFill) {
   1031           _ctx.fill_round_rect(rect.w, rect.y, rect.w, rect.h, r.x, r.y, style);
   1032         }
   1033         else {
   1034           _ctx.stroke_round_rect(rect.w, rect.y, rect.w, rect.h, r.x, r.y, style);
   1035         }
   1036       }
   1037       else {
   1038         if constexpr (kOp == Op::kFill) {
   1039           _ctx.set_fill_style(style);
   1040           _ctx.fill_round_rect(rect.w, rect.y, rect.w, rect.h, r.x, r.y);
   1041         }
   1042         else {
   1043           _ctx.set_stroke_style(style);
   1044           _ctx.stroke_round_rect(rect.w, rect.y, rect.w, rect.h, r.x, r.y);
   1045         }
   1046       }
   1047 
   1048       record_iteration(i);
   1049     }
   1050   }
   1051 
   1052   template<Op kOp>
   1053   void render_triangle(size_t n) {
   1054     for (size_t i = 0; i < n; i++) {
   1055       StyleId style_id = next_style_id();
   1056 
   1057       setup_common_options(_ctx);
   1058       setup_style_options(_ctx, style_id);
   1059 
   1060       BLTriangle t = _rnd.next_triangle();
   1061       BLVar style = materialize_style(style_id);
   1062 
   1063       if (next_style_op() == StyleOp::kExplicit) {
   1064         if constexpr (kOp == Op::kFill) {
   1065           _ctx.fill_triangle(t, style);
   1066         }
   1067         else {
   1068           _ctx.stroke_triangle(t, style);
   1069         }
   1070       }
   1071       else {
   1072         if constexpr (kOp == Op::kFill) {
   1073           _ctx.set_fill_style(style);
   1074           _ctx.fill_triangle(t);
   1075         }
   1076         else {
   1077           _ctx.set_stroke_style(style);
   1078           _ctx.stroke_triangle(t);
   1079         }
   1080       }
   1081 
   1082       record_iteration(i);
   1083     }
   1084   }
   1085 
   1086   template<Op kOp>
   1087   void render_poly_10(size_t n) {
   1088     constexpr uint32_t kPointCount = 10;
   1089     BLPoint pt[kPointCount];
   1090 
   1091     BLString s;
   1092 
   1093     for (size_t i = 0; i < n; i++) {
   1094       StyleId style_id = next_style_id();
   1095 
   1096       setup_common_options(_ctx);
   1097       setup_style_options(_ctx, style_id);
   1098 
   1099       for (uint32_t j = 0; j < kPointCount; j++)
   1100         pt[j] = _rnd.next_point_d();
   1101 
   1102       BLVar style = materialize_style(style_id);
   1103 
   1104       if (next_style_op() == StyleOp::kExplicit) {
   1105         if constexpr (kOp == Op::kFill) {
   1106           _ctx.fill_polygon(pt, kPointCount, style);
   1107         }
   1108         else {
   1109           _ctx.stroke_polygon(pt, kPointCount, style);
   1110         }
   1111       }
   1112       else {
   1113         if constexpr (kOp == Op::kFill) {
   1114           _ctx.set_fill_style(style);
   1115           _ctx.fill_polygon(pt, kPointCount);
   1116         }
   1117         else {
   1118           _ctx.set_stroke_style(style);
   1119           _ctx.stroke_polygon(pt, kPointCount);
   1120         }
   1121       }
   1122       record_iteration(i);
   1123     }
   1124   }
   1125 
   1126   template<Op kOp>
   1127   void render_path_quads(size_t n) {
   1128     for (size_t i = 0; i < n; i++) {
   1129       StyleId style_id = next_style_id();
   1130 
   1131       setup_common_options(_ctx);
   1132       setup_style_options(_ctx, style_id);
   1133 
   1134       BLPath path;
   1135       path.move_to(_rnd.next_point_d());
   1136       path.quad_to(_rnd.next_point_d(), _rnd.next_point_d());
   1137 
   1138       render_path<kOp>(path, style_id);
   1139       record_iteration(i);
   1140     }
   1141   }
   1142 
   1143   template<Op kOp>
   1144   void render_path_cubics(size_t n) {
   1145     for (size_t i = 0; i < n; i++) {
   1146       StyleId style_id = next_style_id();
   1147 
   1148       setup_common_options(_ctx);
   1149       setup_style_options(_ctx, style_id);
   1150 
   1151       BLPath path;
   1152       path.move_to(_rnd.next_point_d());
   1153       path.cubic_to(_rnd.next_point_d(), _rnd.next_point_d(), _rnd.next_point_d());
   1154 
   1155       render_path<kOp>(path, style_id);
   1156       record_iteration(i);
   1157     }
   1158   }
   1159 
   1160   template<Op kOp>
   1161   void render_text(size_t n, uint32_t face_index, float font_size) {
   1162     static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()_{}:;<>?|";
   1163 
   1164     for (size_t i = 0; i < n; i++) {
   1165       StyleId style_id = next_style_id();
   1166 
   1167       setup_common_options(_ctx);
   1168       setup_style_options(_ctx, style_id);
   1169 
   1170       BLFontFace face;
   1171       face.create_from_data(_font_data, face_index);
   1172 
   1173       BLFont font;
   1174       font.create_from_face(face, font_size);
   1175 
   1176       // We want to render at least two text runs so there is a chance that text processing
   1177       // and rendering happens in parallel in case the rendering context uses multi-threading.
   1178       uint32_t rnd0 = _rnd.next_uint32();
   1179       uint32_t rnd1 = _rnd.next_uint32();
   1180 
   1181       char str0[5] {};
   1182       str0[0] = alphabet[((rnd0 >>  0) & 0xFF) % (sizeof(alphabet) - 1u)];
   1183       str0[1] = alphabet[((rnd0 >>  8) & 0xFF) % (sizeof(alphabet) - 1u)];
   1184       str0[2] = alphabet[((rnd0 >> 16) & 0xFF) % (sizeof(alphabet) - 1u)];
   1185       str0[3] = alphabet[((rnd0 >> 24) & 0xFF) % (sizeof(alphabet) - 1u)];
   1186 
   1187       char str1[5] {};
   1188       str1[0] = alphabet[((rnd1 >>  0) & 0xFF) % (sizeof(alphabet) - 1u)];
   1189       str1[1] = alphabet[((rnd1 >>  8) & 0xFF) % (sizeof(alphabet) - 1u)];
   1190       str1[2] = alphabet[((rnd1 >> 16) & 0xFF) % (sizeof(alphabet) - 1u)];
   1191       str1[3] = alphabet[((rnd1 >> 24) & 0xFF) % (sizeof(alphabet) - 1u)];
   1192 
   1193       BLPoint pt0 = _rnd.next_point_d();
   1194       BLPoint pt1 = _rnd.next_point_d();
   1195       BLVar style = materialize_style(style_id);
   1196 
   1197       if (next_style_op() == StyleOp::kExplicit) {
   1198         if constexpr (kOp == Op::kFill) {
   1199           _ctx.fill_utf8_text(pt0, font, BLStringView{str0, 4}, style);
   1200           _ctx.fill_utf8_text(pt1, font, BLStringView{str1, 4}, style);
   1201         }
   1202         else {
   1203           _ctx.stroke_utf8_text(pt0, font, BLStringView{str0, 4}, style);
   1204           _ctx.stroke_utf8_text(pt1, font, BLStringView{str1, 4}, style);
   1205         }
   1206       }
   1207       else {
   1208         if constexpr (kOp == Op::kFill) {
   1209           _ctx.set_fill_style(style);
   1210           _ctx.fill_utf8_text(pt0, font, BLStringView{str0, 4});
   1211           _ctx.fill_utf8_text(pt1, font, BLStringView{str1, 4});
   1212         }
   1213         else {
   1214           _ctx.set_stroke_style(style);
   1215           _ctx.stroke_utf8_text(pt0, font, BLStringView{str0, 4});
   1216           _ctx.stroke_utf8_text(pt1, font, BLStringView{str1, 4});
   1217         }
   1218       }
   1219 
   1220       record_iteration(i);
   1221     }
   1222   }
   1223 };
   1224 
   1225 } // {ContextTests}
   1226 
   1227 #endif // BLEND2D_TEST_CONTEXT_UTILITIES_H_INCLUDED