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