1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <regex> 5 #include <set> 6 #include <chrono> 7 #include <cstdint> 8 #include <cinttypes> 9 10 #include <sys/select.h> 11 12 #include <kms++/kms++.h> 13 #include <kms++/modedb.h> 14 #include <kms++/mode_cvt.h> 15 16 #include <kms++util/kms++util.h> 17 18 using namespace std; 19 using namespace kms; 20 21 struct PropInfo { 22 PropInfo(string n, uint64_t v) : prop(NULL), name(n), val(v) {} 23 24 Property *prop; 25 string name; 26 uint64_t val; 27 }; 28 29 struct PlaneInfo 30 { 31 Plane* plane; 32 33 unsigned x; 34 unsigned y; 35 unsigned w; 36 unsigned h; 37 38 unsigned view_x; 39 unsigned view_y; 40 unsigned view_w; 41 unsigned view_h; 42 43 vector<Framebuffer*> fbs; 44 45 vector<PropInfo> props; 46 }; 47 48 struct OutputInfo 49 { 50 Connector* connector; 51 52 Crtc* crtc; 53 Videomode mode; 54 vector<Framebuffer*> legacy_fbs; 55 56 vector<PlaneInfo> planes; 57 58 vector<PropInfo> conn_props; 59 vector<PropInfo> crtc_props; 60 }; 61 62 static bool s_use_dmt; 63 static bool s_use_cea; 64 static unsigned s_num_buffers = 1; 65 static bool s_flip_mode; 66 static bool s_flip_sync; 67 static bool s_cvt; 68 static bool s_cvt_v2; 69 static bool s_cvt_vid_opt; 70 static unsigned s_max_flips; 71 72 __attribute__ ((unused)) 73 static void print_regex_match(smatch sm) 74 { 75 for (unsigned i = 0; i < sm.size(); ++i) { 76 string str = sm[i].str(); 77 printf("%u: %s\n", i, str.c_str()); 78 } 79 } 80 81 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "") 82 { 83 Connector* conn = resman.reserve_connector(str); 84 85 if (!conn) 86 EXIT("No connector '%s'", str.c_str()); 87 88 if (!conn->connected()) 89 EXIT("Connector '%s' not connected", conn->fullname().c_str()); 90 91 output.connector = conn; 92 output.mode = output.connector->get_default_mode(); 93 } 94 95 static void get_default_crtc(ResourceManager& resman, OutputInfo& output) 96 { 97 output.crtc = resman.reserve_crtc(output.connector); 98 99 if (!output.crtc) 100 EXIT("Could not find available crtc"); 101 } 102 103 104 static PlaneInfo *add_default_planeinfo(OutputInfo* output) 105 { 106 output->planes.push_back(PlaneInfo { }); 107 PlaneInfo *ret = &output->planes.back(); 108 ret->w = output->mode.hdisplay; 109 ret->h = output->mode.vdisplay; 110 return ret; 111 } 112 113 static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output) 114 { 115 // @12:1920x1200i@60 116 // @12:33000000,800/210/30/16/-,480/22/13/10/-,i 117 118 const regex modename_re("(?:(@?)(\\d+):)?" // @12: 119 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i 120 "(?:@([\\d\\.]+))?"); // @60 121 122 const regex modeline_re("(?:(@?)(\\d+):)?" // @12: 123 "(\\d+)," // 33000000, 124 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])," // 800/210/30/16/-, 125 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])" // 480/22/13/10/- 126 "(?:,([i]+))?" // ,i 127 ); 128 129 smatch sm; 130 if (regex_match(crtc_str, sm, modename_re)) { 131 if (sm[2].matched) { 132 bool use_id = sm[1].length() == 1; 133 unsigned num = stoul(sm[2].str()); 134 135 if (use_id) { 136 Crtc* c = card.get_crtc(num); 137 if (!c) 138 EXIT("Bad crtc id '%u'", num); 139 140 output.crtc = c; 141 } else { 142 auto crtcs = card.get_crtcs(); 143 144 if (num >= crtcs.size()) 145 EXIT("Bad crtc number '%u'", num); 146 147 output.crtc = crtcs[num]; 148 } 149 } else { 150 output.crtc = output.connector->get_current_crtc(); 151 } 152 153 unsigned w = stoul(sm[3]); 154 unsigned h = stoul(sm[4]); 155 bool ilace = sm[5].matched ? true : false; 156 float refresh = sm[6].matched ? stof(sm[6]) : 0; 157 158 if (s_cvt) { 159 output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt); 160 } else if (s_use_dmt) { 161 try { 162 output.mode = find_dmt(w, h, refresh, ilace); 163 } catch (exception& e) { 164 EXIT("Mode not found from DMT tables\n"); 165 } 166 } else if (s_use_cea) { 167 try { 168 output.mode = find_cea(w, h, refresh, ilace); 169 } catch (exception& e) { 170 EXIT("Mode not found from CEA tables\n"); 171 } 172 } else { 173 try { 174 output.mode = output.connector->get_mode(w, h, refresh, ilace); 175 } catch (exception& e) { 176 EXIT("Mode not found from the connector\n"); 177 } 178 } 179 } else if (regex_match(crtc_str, sm, modeline_re)) { 180 if (sm[2].matched) { 181 bool use_id = sm[1].length() == 1; 182 unsigned num = stoul(sm[2].str()); 183 184 if (use_id) { 185 Crtc* c = card.get_crtc(num); 186 if (!c) 187 EXIT("Bad crtc id '%u'", num); 188 189 output.crtc = c; 190 } else { 191 auto crtcs = card.get_crtcs(); 192 193 if (num >= crtcs.size()) 194 EXIT("Bad crtc number '%u'", num); 195 196 output.crtc = crtcs[num]; 197 } 198 } else { 199 output.crtc = output.connector->get_current_crtc(); 200 } 201 202 unsigned clock = stoul(sm[3]); 203 204 unsigned hact = stoul(sm[4]); 205 unsigned hfp = stoul(sm[5]); 206 unsigned hsw = stoul(sm[6]); 207 unsigned hbp = stoul(sm[7]); 208 bool h_pos_sync = sm[8] == "+" ? true : false; 209 210 unsigned vact = stoul(sm[9]); 211 unsigned vfp = stoul(sm[10]); 212 unsigned vsw = stoul(sm[11]); 213 unsigned vbp = stoul(sm[12]); 214 bool v_pos_sync = sm[13] == "+" ? true : false; 215 216 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp); 217 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative); 218 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative); 219 220 if (sm[14].matched) { 221 for (int i = 0; i < sm[14].length(); ++i) { 222 char f = string(sm[14])[i]; 223 224 switch (f) { 225 case 'i': 226 output.mode.set_interlace(true); 227 break; 228 default: 229 EXIT("Bad mode flag %c", f); 230 } 231 } 232 } 233 } else { 234 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str()); 235 } 236 237 if (!resman.reserve_crtc(output.crtc)) 238 EXIT("Could not find available crtc"); 239 } 240 241 static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo) 242 { 243 // 3:400,400-400x400 244 const regex plane_re("(?:(@?)(\\d+):)?" // 3: 245 "(?:(\\d+),(\\d+)-)?" // 400,400- 246 "(\\d+)x(\\d+)"); // 400x400 247 248 smatch sm; 249 if (!regex_match(plane_str, sm, plane_re)) 250 EXIT("Failed to parse plane option '%s'", plane_str.c_str()); 251 252 if (sm[2].matched) { 253 bool use_id = sm[1].length() == 1; 254 unsigned num = stoul(sm[2].str()); 255 256 if (use_id) { 257 Plane* p = card.get_plane(num); 258 if (!p) 259 EXIT("Bad plane id '%u'", num); 260 261 pinfo.plane = p; 262 } else { 263 auto planes = card.get_planes(); 264 265 if (num >= planes.size()) 266 EXIT("Bad plane number '%u'", num); 267 268 pinfo.plane = planes[num]; 269 } 270 271 auto plane = resman.reserve_plane(pinfo.plane); 272 if (!plane) 273 EXIT("Plane id %u is not available", pinfo.plane->id()); 274 } 275 276 pinfo.w = stoul(sm[5]); 277 pinfo.h = stoul(sm[6]); 278 279 if (sm[3].matched) 280 pinfo.x = stoul(sm[3]); 281 else 282 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2; 283 284 if (sm[4].matched) 285 pinfo.y = stoul(sm[4]); 286 else 287 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2; 288 } 289 290 static void parse_prop(const string& prop_str, vector<PropInfo> &props) 291 { 292 string name, val; 293 294 size_t split = prop_str.find("="); 295 296 if (split == string::npos) 297 EXIT("Equal sign ('=') not found in %s", prop_str.c_str()); 298 299 name = prop_str.substr(0, split); 300 val = prop_str.substr(split+1); 301 302 props.push_back(PropInfo(name, stoull(val, 0, 0))); 303 } 304 305 static void get_props(Card& card, vector<PropInfo> &props, const DrmPropObject* propobj) 306 { 307 for (auto& pi : props) 308 pi.prop = propobj->get_prop(pi.name); 309 } 310 311 static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height) 312 { 313 vector<Framebuffer*> v; 314 315 for (unsigned i = 0; i < s_num_buffers; ++i) 316 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888)); 317 318 return v; 319 } 320 321 static void parse_fb(Card& card, const string& fb_str, OutputInfo* output, PlaneInfo* pinfo) 322 { 323 unsigned w, h; 324 PixelFormat format = PixelFormat::XRGB8888; 325 326 if (pinfo) { 327 w = pinfo->w; 328 h = pinfo->h; 329 } else { 330 w = output->mode.hdisplay; 331 h = output->mode.vdisplay; 332 } 333 334 if (!fb_str.empty()) { 335 // XXX the regexp is not quite correct 336 // 400x400-NV12 337 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400 338 "(?:-)?" // - 339 "(\\w\\w\\w\\w)?"); // NV12 340 341 smatch sm; 342 if (!regex_match(fb_str, sm, fb_re)) 343 EXIT("Failed to parse fb option '%s'", fb_str.c_str()); 344 345 if (sm[1].matched) 346 w = stoul(sm[1]); 347 if (sm[2].matched) 348 h = stoul(sm[2]); 349 if (sm[3].matched) 350 format = FourCCToPixelFormat(sm[3]); 351 } 352 353 vector<Framebuffer*> v; 354 355 for (unsigned i = 0; i < s_num_buffers; ++i) 356 v.push_back(new DumbFramebuffer(card, w, h, format)); 357 358 if (pinfo) 359 pinfo->fbs = v; 360 else 361 output->legacy_fbs = v; 362 } 363 364 static void parse_view(const string& view_str, PlaneInfo& pinfo) 365 { 366 const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)"); // 400,400-400x400 367 368 smatch sm; 369 if (!regex_match(view_str, sm, view_re)) 370 EXIT("Failed to parse view option '%s'", view_str.c_str()); 371 372 pinfo.view_x = stoul(sm[1]); 373 pinfo.view_y = stoul(sm[2]); 374 pinfo.view_w = stoul(sm[3]); 375 pinfo.view_h = stoul(sm[4]); 376 } 377 378 static const char* usage_str = 379 "Usage: kmstest [OPTION]...\n\n" 380 "Show a test pattern on a display or plane\n\n" 381 "Options:\n" 382 " --device=DEVICE DEVICE is the path to DRM card to open\n" 383 " -c, --connector=CONN CONN is <connector>\n" 384 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n" 385 " or\n" 386 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n" 387 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n" 388 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n" 389 " -v, --view=VIEW VIEW is <x>,<y>-<w>x<h>\n" 390 " -P, --property=PROP=VAL Set PROP to VAL in the previous DRM object\n" 391 " --dmt Search for the given mode from DMT tables\n" 392 " --cea Search for the given mode from CEA tables\n" 393 " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n" 394 " --flip[=max] Do page flipping for each output with an optional maximum flips count\n" 395 " --sync Synchronize page flipping\n" 396 "\n" 397 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n" 398 "<connector> can also be given by name.\n" 399 "\n" 400 "Options can be given multiple times to set up multiple displays or planes.\n" 401 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n" 402 "an earlier option.\n" 403 "If you omit parameters, kmstest tries to guess what you mean\n" 404 "\n" 405 "Examples:\n" 406 "\n" 407 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n" 408 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n" 409 "XR24 framebuffer on first connected connector in the default mode:\n" 410 " kmstest -f XR24\n\n" 411 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n" 412 " kmstest -p 400x400 -f XR24\n\n" 413 "Test pattern on the second connector with default mode:\n" 414 " kmstest -c 1\n" 415 "\n" 416 "Environmental variables:\n" 417 " KMSXX_DISABLE_UNIVERSAL_PLANES Don't enable universal planes even if available\n" 418 " KMSXX_DISABLE_ATOMIC Don't enable atomic modesetting even if available\n" 419 ; 420 421 static void usage() 422 { 423 puts(usage_str); 424 } 425 426 enum class ArgType 427 { 428 Connector, 429 Crtc, 430 Plane, 431 Framebuffer, 432 View, 433 Property, 434 }; 435 436 struct Arg 437 { 438 ArgType type; 439 string arg; 440 }; 441 442 static string s_device_path = "/dev/dri/card0"; 443 444 static vector<Arg> parse_cmdline(int argc, char **argv) 445 { 446 vector<Arg> args; 447 448 OptionSet optionset = { 449 Option("|device=", 450 [&](string s) 451 { 452 s_device_path = s; 453 }), 454 Option("c|connector=", 455 [&](string s) 456 { 457 args.push_back(Arg { ArgType::Connector, s }); 458 }), 459 Option("r|crtc=", [&](string s) 460 { 461 args.push_back(Arg { ArgType::Crtc, s }); 462 }), 463 Option("p|plane=", [&](string s) 464 { 465 args.push_back(Arg { ArgType::Plane, s }); 466 }), 467 Option("f|fb=", [&](string s) 468 { 469 args.push_back(Arg { ArgType::Framebuffer, s }); 470 }), 471 Option("v|view=", [&](string s) 472 { 473 args.push_back(Arg { ArgType::View, s }); 474 }), 475 Option("P|property=", [&](string s) 476 { 477 args.push_back(Arg { ArgType::Property, s }); 478 }), 479 Option("|dmt", []() 480 { 481 s_use_dmt = true; 482 }), 483 Option("|cea", []() 484 { 485 s_use_cea = true; 486 }), 487 Option("|flip?", [&](string s) 488 { 489 s_flip_mode = true; 490 s_num_buffers = 2; 491 if (!s.empty()) 492 s_max_flips = stoi(s); 493 }), 494 Option("|sync", []() 495 { 496 s_flip_sync = true; 497 }), 498 Option("|cvt=", [&](string s) 499 { 500 if (s == "v1") 501 s_cvt = true; 502 else if (s == "v2") 503 s_cvt = s_cvt_v2 = true; 504 else if (s == "v2o") 505 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true; 506 else { 507 usage(); 508 exit(-1); 509 } 510 }), 511 Option("h|help", [&]() 512 { 513 usage(); 514 exit(-1); 515 }), 516 }; 517 518 optionset.parse(argc, argv); 519 520 if (optionset.params().size() > 0) { 521 usage(); 522 exit(-1); 523 } 524 525 return args; 526 } 527 528 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args) 529 { 530 vector<OutputInfo> outputs; 531 532 OutputInfo* current_output = 0; 533 PlaneInfo* current_plane = 0; 534 535 for (auto& arg : output_args) { 536 switch (arg.type) { 537 case ArgType::Connector: 538 { 539 outputs.push_back(OutputInfo { }); 540 current_output = &outputs.back(); 541 542 get_connector(resman, *current_output, arg.arg); 543 current_plane = 0; 544 545 break; 546 } 547 548 case ArgType::Crtc: 549 { 550 if (!current_output) { 551 outputs.push_back(OutputInfo { }); 552 current_output = &outputs.back(); 553 } 554 555 if (!current_output->connector) 556 get_connector(resman, *current_output); 557 558 parse_crtc(resman, card, arg.arg, *current_output); 559 560 current_plane = 0; 561 562 break; 563 } 564 565 case ArgType::Plane: 566 { 567 if (!current_output) { 568 outputs.push_back(OutputInfo { }); 569 current_output = &outputs.back(); 570 } 571 572 if (!current_output->connector) 573 get_connector(resman, *current_output); 574 575 if (!current_output->crtc) 576 get_default_crtc(resman, *current_output); 577 578 current_plane = add_default_planeinfo(current_output); 579 580 parse_plane(resman, card, arg.arg, *current_output, *current_plane); 581 582 break; 583 } 584 585 case ArgType::Framebuffer: 586 { 587 if (!current_output) { 588 outputs.push_back(OutputInfo { }); 589 current_output = &outputs.back(); 590 } 591 592 if (!current_output->connector) 593 get_connector(resman, *current_output); 594 595 if (!current_output->crtc) 596 get_default_crtc(resman, *current_output); 597 598 if (!current_plane && card.has_atomic()) 599 current_plane = add_default_planeinfo(current_output); 600 601 parse_fb(card, arg.arg, current_output, current_plane); 602 603 break; 604 } 605 606 case ArgType::View: 607 { 608 if (!current_plane || current_plane->fbs.empty()) 609 EXIT("'view' parameter requires a plane and a fb"); 610 611 parse_view(arg.arg, *current_plane); 612 break; 613 } 614 615 case ArgType::Property: 616 { 617 if (!current_output) 618 EXIT("No object to which set the property"); 619 620 if (current_plane) 621 parse_prop(arg.arg, current_plane->props); 622 else if (current_output->crtc) 623 parse_prop(arg.arg, current_output->crtc_props); 624 else if (current_output->connector) 625 parse_prop(arg.arg, current_output->conn_props); 626 else 627 EXIT("no object"); 628 629 break; 630 } 631 } 632 } 633 634 if (outputs.empty()) { 635 // no outputs defined, show a pattern on all screens 636 for (Connector* conn : card.get_connectors()) { 637 if (!conn->connected()) 638 continue; 639 640 OutputInfo output = { }; 641 output.connector = resman.reserve_connector(conn); 642 EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str()); 643 output.crtc = resman.reserve_crtc(conn); 644 EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str()); 645 output.mode = output.connector->get_default_mode(); 646 647 outputs.push_back(output); 648 } 649 } 650 651 for (OutputInfo& o : outputs) { 652 get_props(card, o.conn_props, o.connector); 653 654 if (!o.crtc) 655 get_default_crtc(resman, o); 656 657 get_props(card, o.crtc_props, o.crtc); 658 659 if (card.has_atomic()) { 660 if (o.planes.empty()) 661 add_default_planeinfo(&o); 662 } else { 663 if (o.legacy_fbs.empty()) 664 o.legacy_fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay); 665 } 666 667 for (PlaneInfo &p : o.planes) { 668 if (p.fbs.empty()) 669 p.fbs = get_default_fb(card, p.w, p.h); 670 } 671 672 for (PlaneInfo& p : o.planes) { 673 if (!p.plane) { 674 if (card.has_atomic()) 675 p.plane = resman.reserve_generic_plane(o.crtc, p.fbs[0]->format()); 676 else 677 p.plane = resman.reserve_overlay_plane(o.crtc, p.fbs[0]->format()); 678 679 if (!p.plane) 680 EXIT("Failed to find available plane"); 681 } 682 get_props(card, p.props, p.plane); 683 } 684 } 685 686 return outputs; 687 } 688 689 static std::string videomode_to_string(const Videomode& m) 690 { 691 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp()); 692 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp()); 693 694 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x", 695 m.name.c_str(), 696 m.clock / 1000.0, 697 h.c_str(), v.c_str(), 698 m.vrefresh, m.calculated_vrefresh(), 699 m.flags, 700 m.type); 701 } 702 703 static void print_outputs(const vector<OutputInfo>& outputs) 704 { 705 for (unsigned i = 0; i < outputs.size(); ++i) { 706 const OutputInfo& o = outputs[i]; 707 708 printf("Connector %u/@%u: %s", o.connector->idx(), o.connector->id(), 709 o.connector->fullname().c_str()); 710 711 for (const PropInfo &prop: o.conn_props) 712 printf(" %s=%" PRIu64, prop.prop->name().c_str(), 713 prop.val); 714 715 printf("\n Crtc %u/@%u", o.crtc->idx(), o.crtc->id()); 716 717 for (const PropInfo &prop: o.crtc_props) 718 printf(" %s=%" PRIu64, prop.prop->name().c_str(), 719 prop.val); 720 721 printf(": %s\n", videomode_to_string(o.mode).c_str()); 722 723 if (!o.legacy_fbs.empty()) { 724 auto fb = o.legacy_fbs[0]; 725 printf(" (Fb %u %ux%u-%s)", fb->id(), fb->width(), fb->height(), PixelFormatToFourCC(fb->format()).c_str()); 726 } 727 728 for (unsigned j = 0; j < o.planes.size(); ++j) { 729 const PlaneInfo& p = o.planes[j]; 730 auto fb = p.fbs[0]; 731 printf(" Plane %u/@%u: %u,%u-%ux%u", p.plane->idx(), p.plane->id(), 732 p.x, p.y, p.w, p.h); 733 for (const PropInfo &prop: p.props) 734 printf(" %s=%" PRIu64, prop.prop->name().c_str(), 735 prop.val); 736 printf("\n"); 737 738 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(), 739 PixelFormatToFourCC(fb->format()).c_str()); 740 } 741 } 742 } 743 744 static void draw_test_patterns(const vector<OutputInfo>& outputs) 745 { 746 for (const OutputInfo& o : outputs) { 747 for (auto fb : o.legacy_fbs) 748 draw_test_pattern(*fb); 749 750 for (const PlaneInfo& p : o.planes) 751 for (auto fb : p.fbs) 752 draw_test_pattern(*fb); 753 } 754 } 755 756 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs) 757 { 758 // Disable unused crtcs 759 for (Crtc* crtc : card.get_crtcs()) { 760 if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end()) 761 continue; 762 763 crtc->disable_mode(); 764 } 765 766 for (const OutputInfo& o : outputs) { 767 auto conn = o.connector; 768 auto crtc = o.crtc; 769 770 if (!o.conn_props.empty() || !o.crtc_props.empty()) 771 printf("WARNING: properties not set without atomic modesetting"); 772 773 if (!o.legacy_fbs.empty()) { 774 auto fb = o.legacy_fbs[0]; 775 int r = crtc->set_mode(conn, *fb, o.mode); 776 if (r) 777 printf("crtc->set_mode() failed for crtc %u: %s\n", 778 crtc->id(), strerror(-r)); 779 } 780 781 for (const PlaneInfo& p : o.planes) { 782 auto fb = p.fbs[0]; 783 int r = crtc->set_plane(p.plane, *fb, 784 p.x, p.y, p.w, p.h, 785 0, 0, fb->width(), fb->height()); 786 if (r) 787 printf("crtc->set_plane() failed for plane %u: %s\n", 788 p.plane->id(), strerror(-r)); 789 if (!p.props.empty()) 790 printf("WARNING: properties not set without atomic modesetting"); 791 } 792 } 793 } 794 795 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs) 796 { 797 int r; 798 799 // XXX DRM framework doesn't allow moving an active plane from one crtc to another. 800 // See drm_atomic.c::plane_switching_crtc(). 801 // For the time being, disable all crtcs and planes here. 802 803 AtomicReq disable_req(card); 804 805 // Disable unused crtcs 806 for (Crtc* crtc : card.get_crtcs()) { 807 //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end()) 808 // continue; 809 810 disable_req.add(crtc, { 811 { "ACTIVE", 0 }, 812 }); 813 } 814 815 // Disable unused planes 816 for (Plane* plane : card.get_planes()) 817 disable_req.add(plane, { 818 { "FB_ID", 0 }, 819 { "CRTC_ID", 0 }, 820 }); 821 822 r = disable_req.commit_sync(true); 823 if (r) 824 EXIT("Atomic commit failed when disabling: %d\n", r); 825 826 827 // Keep blobs here so that we keep ref to them until we have committed the req 828 vector<unique_ptr<Blob>> blobs; 829 830 AtomicReq req(card); 831 832 for (const OutputInfo& o : outputs) { 833 auto conn = o.connector; 834 auto crtc = o.crtc; 835 836 blobs.emplace_back(o.mode.to_blob(card)); 837 Blob* mode_blob = blobs.back().get(); 838 839 req.add(conn, { 840 { "CRTC_ID", crtc->id() }, 841 }); 842 843 for (const PropInfo &prop: o.conn_props) 844 req.add(conn, prop.prop, prop.val); 845 846 req.add(crtc, { 847 { "ACTIVE", 1 }, 848 { "MODE_ID", mode_blob->id() }, 849 }); 850 851 for (const PropInfo &prop: o.crtc_props) 852 req.add(crtc, prop.prop, prop.val); 853 854 for (const PlaneInfo& p : o.planes) { 855 auto fb = p.fbs[0]; 856 857 req.add(p.plane, { 858 { "FB_ID", fb->id() }, 859 { "CRTC_ID", crtc->id() }, 860 { "SRC_X", (p.view_x ?: 0) << 16 }, 861 { "SRC_Y", (p.view_y ?: 0) << 16 }, 862 { "SRC_W", (p.view_w ?: fb->width()) << 16 }, 863 { "SRC_H", (p.view_h ?: fb->height()) << 16 }, 864 { "CRTC_X", p.x }, 865 { "CRTC_Y", p.y }, 866 { "CRTC_W", p.w }, 867 { "CRTC_H", p.h }, 868 }); 869 870 for (const PropInfo &prop: p.props) 871 req.add(p.plane, prop.prop, prop.val); 872 } 873 } 874 875 r = req.test(true); 876 if (r) 877 EXIT("Atomic test failed: %d\n", r); 878 879 r = req.commit_sync(true); 880 if (r) 881 EXIT("Atomic commit failed: %d\n", r); 882 } 883 884 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs) 885 { 886 if (card.has_atomic()) 887 set_crtcs_n_planes_atomic(card, outputs); 888 else 889 set_crtcs_n_planes_legacy(card, outputs); 890 } 891 892 static bool max_flips_reached; 893 894 class FlipState : private PageFlipHandlerBase 895 { 896 public: 897 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs) 898 : m_card(card), m_name(name), m_outputs(outputs) 899 { 900 } 901 902 void start_flipping() 903 { 904 m_prev_frame = m_prev_print = std::chrono::steady_clock::now(); 905 m_slowest_frame = std::chrono::duration<float>::min(); 906 m_frame_num = 0; 907 queue_next(); 908 } 909 910 private: 911 void handle_page_flip(uint32_t frame, double time) 912 { 913 /* 914 * We get flip event for each crtc in this flipstate. We can commit the next frames 915 * only after we've gotten the flip event for all crtcs 916 */ 917 if (++m_flip_count < m_outputs.size()) 918 return; 919 920 m_frame_num++; 921 if (s_max_flips && m_frame_num >= s_max_flips) 922 max_flips_reached = true; 923 924 auto now = std::chrono::steady_clock::now(); 925 926 std::chrono::duration<float> diff = now - m_prev_frame; 927 if (diff > m_slowest_frame) 928 m_slowest_frame = diff; 929 930 if (m_frame_num % 100 == 0) { 931 std::chrono::duration<float> fsec = now - m_prev_print; 932 printf("Connector %s: fps %f, slowest %.2f ms\n", 933 m_name.c_str(), 934 100.0 / fsec.count(), 935 m_slowest_frame.count() * 1000); 936 m_prev_print = now; 937 m_slowest_frame = std::chrono::duration<float>::min(); 938 } 939 940 m_prev_frame = now; 941 942 queue_next(); 943 } 944 945 static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num) 946 { 947 return (frame_num * bar_speed) % (fb->width() - bar_width + 1); 948 } 949 950 static void draw_bar(Framebuffer* fb, unsigned frame_num) 951 { 952 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers); 953 int new_xpos = get_bar_pos(fb, frame_num); 954 955 draw_color_bar(*fb, old_xpos, new_xpos, bar_width); 956 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255)); 957 } 958 959 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o) 960 { 961 unsigned cur = frame_num % s_num_buffers; 962 963 for (const PlaneInfo& p : o.planes) { 964 auto fb = p.fbs[cur]; 965 966 draw_bar(fb, frame_num); 967 968 req.add(p.plane, { 969 { "FB_ID", fb->id() }, 970 }); 971 } 972 } 973 974 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o) 975 { 976 unsigned cur = frame_num % s_num_buffers; 977 978 if (!o.legacy_fbs.empty()) { 979 auto fb = o.legacy_fbs[cur]; 980 981 draw_bar(fb, frame_num); 982 983 int r = o.crtc->page_flip(*fb, this); 984 ASSERT(r == 0); 985 } 986 987 for (const PlaneInfo& p : o.planes) { 988 auto fb = p.fbs[cur]; 989 990 draw_bar(fb, frame_num); 991 992 int r = o.crtc->set_plane(p.plane, *fb, 993 p.x, p.y, p.w, p.h, 994 0, 0, fb->width(), fb->height()); 995 ASSERT(r == 0); 996 } 997 } 998 999 void queue_next() 1000 { 1001 m_flip_count = 0; 1002 1003 if (m_card.has_atomic()) { 1004 AtomicReq req(m_card); 1005 1006 for (auto o : m_outputs) 1007 do_flip_output(req, m_frame_num, *o); 1008 1009 int r = req.commit(this); 1010 if (r) 1011 EXIT("Flip commit failed: %d\n", r); 1012 } else { 1013 ASSERT(m_outputs.size() == 1); 1014 do_flip_output_legacy(m_frame_num, *m_outputs[0]); 1015 } 1016 } 1017 1018 Card& m_card; 1019 string m_name; 1020 vector<const OutputInfo*> m_outputs; 1021 unsigned m_frame_num; 1022 unsigned m_flip_count; 1023 1024 chrono::steady_clock::time_point m_prev_print; 1025 chrono::steady_clock::time_point m_prev_frame; 1026 chrono::duration<float> m_slowest_frame; 1027 1028 static const unsigned bar_width = 20; 1029 static const unsigned bar_speed = 8; 1030 }; 1031 1032 static void main_flip(Card& card, const vector<OutputInfo>& outputs) 1033 { 1034 fd_set fds; 1035 1036 FD_ZERO(&fds); 1037 1038 int fd = card.fd(); 1039 1040 vector<unique_ptr<FlipState>> flipstates; 1041 1042 if (!s_flip_sync) { 1043 for (const OutputInfo& o : outputs) { 1044 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o })); 1045 flipstates.push_back(move(fs)); 1046 } 1047 } else { 1048 vector<const OutputInfo*> ois; 1049 1050 string name; 1051 for (const OutputInfo& o : outputs) { 1052 name += to_string(o.connector->idx()) + ","; 1053 ois.push_back(&o); 1054 } 1055 1056 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois)); 1057 flipstates.push_back(move(fs)); 1058 } 1059 1060 for (unique_ptr<FlipState>& fs : flipstates) 1061 fs->start_flipping(); 1062 1063 while (!max_flips_reached) { 1064 int r; 1065 1066 FD_SET(0, &fds); 1067 FD_SET(fd, &fds); 1068 1069 r = select(fd + 1, &fds, NULL, NULL, NULL); 1070 if (r < 0) { 1071 fprintf(stderr, "select() failed with %d: %m\n", errno); 1072 break; 1073 } else if (FD_ISSET(0, &fds)) { 1074 fprintf(stderr, "Exit due to user-input\n"); 1075 break; 1076 } else if (FD_ISSET(fd, &fds)) { 1077 card.call_page_flip_handlers(); 1078 } 1079 } 1080 } 1081 1082 int main(int argc, char **argv) 1083 { 1084 vector<Arg> output_args = parse_cmdline(argc, argv); 1085 1086 Card card(s_device_path); 1087 1088 if (!card.has_atomic() && s_flip_sync) 1089 EXIT("Synchronized flipping requires atomic modesetting"); 1090 1091 ResourceManager resman(card); 1092 1093 vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args); 1094 1095 if (!s_flip_mode) 1096 draw_test_patterns(outputs); 1097 1098 print_outputs(outputs); 1099 1100 set_crtcs_n_planes(card, outputs); 1101 1102 printf("press enter to exit\n"); 1103 1104 if (s_flip_mode) 1105 main_flip(card, outputs); 1106 else 1107 getchar(); 1108 } 1109