kmstest: Separate reservation phase from command line parsing
[android/external-libkmsxx.git] / utils / kmstest.cpp
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
7 #include <cstdint>
8 #include <cinttypes>
10 #include <sys/select.h>
12 #include <kms++/kms++.h>
13 #include <kms++/modedb.h>
14 #include <kms++/mode_cvt.h>
16 #include <kms++util/kms++util.h>
18 using namespace std;
19 using namespace kms;
21 struct PropInfo {
22         PropInfo(string n, uint64_t v) : prop(NULL), name(n), val(v) {}
24         Property *prop;
25         string name;
26         uint64_t val;
27 };
29 struct PlaneInfo
30 {
31         Plane* plane;
33         unsigned x;
34         unsigned y;
35         unsigned w;
36         unsigned h;
38         unsigned view_x;
39         unsigned view_y;
40         unsigned view_w;
41         unsigned view_h;
43         vector<Framebuffer*> fbs;
45         vector<PropInfo> props;
46 };
48 struct OutputInfo
49 {
50         Connector* connector;
52         Crtc* crtc;
53         Videomode mode;
54         vector<Framebuffer*> legacy_fbs;
56         vector<PlaneInfo> planes;
58         vector<PropInfo> conn_props;
59         vector<PropInfo> crtc_props;
60 };
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;
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 }
81 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
82 {
83         Connector* conn = resman.reserve_connector(str);
85         if (!conn)
86                 EXIT("No connector '%s'", str.c_str());
88         if (!conn->connected())
89                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
91         output.connector = conn;
92         output.mode = output.connector->get_default_mode();
93 }
95 static void get_default_crtc(ResourceManager& resman, OutputInfo& output)
96 {
97         output.crtc = resman.reserve_crtc(output.connector);
99         if (!output.crtc)
100                 EXIT("Could not find available crtc");
104 static PlaneInfo *add_default_planeinfo(OutputInfo* output)
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;
113 static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output)
115         // @12:1920x1200i@60
116         // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
118         const regex modename_re("(?:(@?)(\\d+):)?"      // @12:
119                                 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
120                                 "(?:@([\\d\\.]+))?");   // @60
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                                 );
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());
135                         if (use_id) {
136                                 Crtc* c = card.get_crtc(num);
137                                 if (!c)
138                                         EXIT("Bad crtc id '%u'", num);
140                                 output.crtc = c;
141                         } else {
142                                 auto crtcs = card.get_crtcs();
144                                 if (num >= crtcs.size())
145                                         EXIT("Bad crtc number '%u'", num);
147                                 output.crtc = crtcs[num];
148                         }
149                 } else {
150                         output.crtc = output.connector->get_current_crtc();
151                 }
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;
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());
184                         if (use_id) {
185                                 Crtc* c = card.get_crtc(num);
186                                 if (!c)
187                                         EXIT("Bad crtc id '%u'", num);
189                                 output.crtc = c;
190                         } else {
191                                 auto crtcs = card.get_crtcs();
193                                 if (num >= crtcs.size())
194                                         EXIT("Bad crtc number '%u'", num);
196                                 output.crtc = crtcs[num];
197                         }
198                 } else {
199                         output.crtc = output.connector->get_current_crtc();
200                 }
202                 unsigned clock = stoul(sm[3]);
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;
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;
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);
220                 if (sm[14].matched) {
221                         for (int i = 0; i < sm[14].length(); ++i) {
222                                 char f = string(sm[14])[i];
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         }
237         if (!resman.reserve_crtc(output.crtc))
238                 EXIT("Could not find available crtc");
241 static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
243         // 3:400,400-400x400
244         const regex plane_re("(?:(@?)(\\d+):)?"         // 3:
245                              "(?:(\\d+),(\\d+)-)?"      // 400,400-
246                              "(\\d+)x(\\d+)");          // 400x400
248         smatch sm;
249         if (!regex_match(plane_str, sm, plane_re))
250                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
252         if (sm[2].matched) {
253                 bool use_id = sm[1].length() == 1;
254                 unsigned num = stoul(sm[2].str());
256                 if (use_id) {
257                         Plane* p = card.get_plane(num);
258                         if (!p)
259                                 EXIT("Bad plane id '%u'", num);
261                         pinfo.plane = p;
262                 } else {
263                         auto planes = card.get_planes();
265                         if (num >= planes.size())
266                                 EXIT("Bad plane number '%u'", num);
268                         pinfo.plane = planes[num];
269                 }
271                 auto plane = resman.reserve_plane(pinfo.plane);
272                 if (!plane)
273                         EXIT("Plane id %u is not available", pinfo.plane->id());
274         }
276         pinfo.w = stoul(sm[5]);
277         pinfo.h = stoul(sm[6]);
279         if (sm[3].matched)
280                 pinfo.x = stoul(sm[3]);
281         else
282                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
284         if (sm[4].matched)
285                 pinfo.y = stoul(sm[4]);
286         else
287                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
290 static void parse_prop(const string& prop_str, vector<PropInfo> &props)
292         string name, val;
294         size_t split = prop_str.find("=");
296         if (split == string::npos)
297                 EXIT("Equal sign ('=') not found in %s", prop_str.c_str());
299         name = prop_str.substr(0, split);
300         val = prop_str.substr(split+1);
302         props.push_back(PropInfo(name, stoull(val, 0, 0)));
305 static void get_props(Card& card, vector<PropInfo> &props, const DrmPropObject* propobj)
307         for (auto& pi : props)
308                 pi.prop = propobj->get_prop(pi.name);
311 static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
313         vector<Framebuffer*> v;
315         for (unsigned i = 0; i < s_num_buffers; ++i)
316                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
318         return v;
321 static void parse_fb(Card& card, const string& fb_str, OutputInfo* output, PlaneInfo* pinfo)
323         unsigned w, h;
324         PixelFormat format = PixelFormat::XRGB8888;
326         if (pinfo) {
327                 w = pinfo->w;
328                 h = pinfo->h;
329         } else {
330                 w = output->mode.hdisplay;
331                 h = output->mode.vdisplay;
332         }
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
341                 smatch sm;
342                 if (!regex_match(fb_str, sm, fb_re))
343                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
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         }
353         vector<Framebuffer*> v;
355         for (unsigned i = 0; i < s_num_buffers; ++i)
356                 v.push_back(new DumbFramebuffer(card, w, h, format));
358         if (pinfo)
359                 pinfo->fbs = v;
360         else
361                 output->legacy_fbs = v;
364 static void parse_view(const string& view_str, PlaneInfo& pinfo)
366         const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)");             // 400,400-400x400
368         smatch sm;
369         if (!regex_match(view_str, sm, view_re))
370                 EXIT("Failed to parse view option '%s'", view_str.c_str());
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]);
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                 ;
421 static void usage()
423         puts(usage_str);
426 enum class ArgType
428         Connector,
429         Crtc,
430         Plane,
431         Framebuffer,
432         View,
433         Property,
434 };
436 struct Arg
438         ArgType type;
439         string arg;
440 };
442 static string s_device_path = "/dev/dri/card0";
444 static vector<Arg> parse_cmdline(int argc, char **argv)
446         vector<Arg> args;
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         };
518         optionset.parse(argc, argv);
520         if (optionset.params().size() > 0) {
521                 usage();
522                 exit(-1);
523         }
525         return args;
528 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
530         vector<OutputInfo> outputs;
532         OutputInfo* current_output = 0;
533         PlaneInfo* current_plane = 0;
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();
542                         get_connector(resman, *current_output, arg.arg);
543                         current_plane = 0;
545                         break;
546                 }
548                 case ArgType::Crtc:
549                 {
550                         if (!current_output) {
551                                 outputs.push_back(OutputInfo { });
552                                 current_output = &outputs.back();
553                         }
555                         if (!current_output->connector)
556                                 get_connector(resman, *current_output);
558                         parse_crtc(resman, card, arg.arg, *current_output);
560                         current_plane = 0;
562                         break;
563                 }
565                 case ArgType::Plane:
566                 {
567                         if (!current_output) {
568                                 outputs.push_back(OutputInfo { });
569                                 current_output = &outputs.back();
570                         }
572                         if (!current_output->connector)
573                                 get_connector(resman, *current_output);
575                         if (!current_output->crtc)
576                                 get_default_crtc(resman, *current_output);
578                         current_plane = add_default_planeinfo(current_output);
580                         parse_plane(resman, card, arg.arg, *current_output, *current_plane);
582                         break;
583                 }
585                 case ArgType::Framebuffer:
586                 {
587                         if (!current_output) {
588                                 outputs.push_back(OutputInfo { });
589                                 current_output = &outputs.back();
590                         }
592                         if (!current_output->connector)
593                                 get_connector(resman, *current_output);
595                         if (!current_output->crtc)
596                                 get_default_crtc(resman, *current_output);
598                         if (!current_plane && card.has_atomic())
599                                 current_plane = add_default_planeinfo(current_output);
601                         parse_fb(card, arg.arg, current_output, current_plane);
603                         break;
604                 }
606                 case ArgType::View:
607                 {
608                         if (!current_plane || current_plane->fbs.empty())
609                                 EXIT("'view' parameter requires a plane and a fb");
611                         parse_view(arg.arg, *current_plane);
612                         break;
613                 }
615                 case ArgType::Property:
616                 {
617                         if (!current_output)
618                                 EXIT("No object to which set the property");
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");
629                         break;
630                 }
631                 }
632         }
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;
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();
647                         outputs.push_back(output);
648                 }
649         }
651         for (OutputInfo& o : outputs) {
652                 get_props(card, o.conn_props, o.connector);
654                 if (!o.crtc)
655                         get_default_crtc(resman, o);
657                 get_props(card, o.crtc_props, o.crtc);
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                 }
667                 for (PlaneInfo &p : o.planes) {
668                         if (p.fbs.empty())
669                                 p.fbs = get_default_fb(card, p.w, p.h);
670                 }
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());
679                                 if (!p.plane)
680                                         EXIT("Failed to find available plane");
681                         }
682                         get_props(card, p.props, p.plane);
683                 }
684         }
686         return outputs;
689 static std::string videomode_to_string(const Videomode& m)
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());
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);
703 static void print_outputs(const vector<OutputInfo>& outputs)
705         for (unsigned i = 0; i < outputs.size(); ++i) {
706                 const OutputInfo& o = outputs[i];
708                 printf("Connector %u/@%u: %s", o.connector->idx(), o.connector->id(),
709                        o.connector->fullname().c_str());
711                 for (const PropInfo &prop: o.conn_props)
712                         printf(" %s=%" PRIu64, prop.prop->name().c_str(),
713                                prop.val);
715                 printf("\n  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
717                 for (const PropInfo &prop: o.crtc_props)
718                         printf(" %s=%" PRIu64, prop.prop->name().c_str(),
719                                prop.val);
721                 printf(": %s\n", videomode_to_string(o.mode).c_str());
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                 }
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");
738                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
739                                PixelFormatToFourCC(fb->format()).c_str());
740                 }
741         }
744 static void draw_test_patterns(const vector<OutputInfo>& outputs)
746         for (const OutputInfo& o : outputs) {
747                 for (auto fb : o.legacy_fbs)
748                         draw_test_pattern(*fb);
750                 for (const PlaneInfo& p : o.planes)
751                         for (auto fb : p.fbs)
752                                 draw_test_pattern(*fb);
753         }
756 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
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;
763                 crtc->disable_mode();
764         }
766         for (const OutputInfo& o : outputs) {
767                 auto conn = o.connector;
768                 auto crtc = o.crtc;
770                 if (!o.conn_props.empty() || !o.crtc_props.empty())
771                         printf("WARNING: properties not set without atomic modesetting");
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                 }
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         }
795 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
797         int r;
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.
803         AtomicReq disable_req(card);
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;
810                 disable_req.add(crtc, {
811                                 { "ACTIVE", 0 },
812                         });
813         }
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                         });
822         r = disable_req.commit_sync(true);
823         if (r)
824                 EXIT("Atomic commit failed when disabling: %d\n", r);
827         // Keep blobs here so that we keep ref to them until we have committed the req
828         vector<unique_ptr<Blob>> blobs;
830         AtomicReq req(card);
832         for (const OutputInfo& o : outputs) {
833                 auto conn = o.connector;
834                 auto crtc = o.crtc;
836                 blobs.emplace_back(o.mode.to_blob(card));
837                 Blob* mode_blob = blobs.back().get();
839                 req.add(conn, {
840                                 { "CRTC_ID", crtc->id() },
841                         });
843                 for (const PropInfo &prop: o.conn_props)
844                         req.add(conn, prop.prop, prop.val);
846                 req.add(crtc, {
847                                 { "ACTIVE", 1 },
848                                 { "MODE_ID", mode_blob->id() },
849                         });
851                 for (const PropInfo &prop: o.crtc_props)
852                         req.add(crtc, prop.prop, prop.val);
854                 for (const PlaneInfo& p : o.planes) {
855                         auto fb = p.fbs[0];
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                                 });
870                         for (const PropInfo &prop: p.props)
871                                 req.add(p.plane, prop.prop, prop.val);
872                 }
873         }
875         r = req.test(true);
876         if (r)
877                 EXIT("Atomic test failed: %d\n", r);
879         r = req.commit_sync(true);
880         if (r)
881                 EXIT("Atomic commit failed: %d\n", r);
884 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
886         if (card.has_atomic())
887                 set_crtcs_n_planes_atomic(card, outputs);
888         else
889                 set_crtcs_n_planes_legacy(card, outputs);
892 static bool max_flips_reached;
894 class FlipState : private PageFlipHandlerBase
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         }
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         }
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;
920                 m_frame_num++;
921                 if (s_max_flips && m_frame_num >= s_max_flips)
922                         max_flips_reached = true;
924                 auto now = std::chrono::steady_clock::now();
926                 std::chrono::duration<float> diff = now - m_prev_frame;
927                 if (diff > m_slowest_frame)
928                         m_slowest_frame = diff;
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                 }
940                 m_prev_frame = now;
942                 queue_next();
943         }
945         static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num)
946         {
947                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
948         }
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);
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         }
959         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
960         {
961                 unsigned cur = frame_num % s_num_buffers;
963                 for (const PlaneInfo& p : o.planes) {
964                         auto fb = p.fbs[cur];
966                         draw_bar(fb, frame_num);
968                         req.add(p.plane, {
969                                         { "FB_ID", fb->id() },
970                                 });
971                 }
972         }
974         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
975         {
976                 unsigned cur = frame_num % s_num_buffers;
978                 if (!o.legacy_fbs.empty()) {
979                         auto fb = o.legacy_fbs[cur];
981                         draw_bar(fb, frame_num);
983                         int r = o.crtc->page_flip(*fb, this);
984                         ASSERT(r == 0);
985                 }
987                 for (const PlaneInfo& p : o.planes) {
988                         auto fb = p.fbs[cur];
990                         draw_bar(fb, frame_num);
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         }
999         void queue_next()
1000         {
1001                 m_flip_count = 0;
1003                 if (m_card.has_atomic()) {
1004                         AtomicReq req(m_card);
1006                         for (auto o : m_outputs)
1007                                 do_flip_output(req, m_frame_num, *o);
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         }
1018         Card& m_card;
1019         string m_name;
1020         vector<const OutputInfo*> m_outputs;
1021         unsigned m_frame_num;
1022         unsigned m_flip_count;
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;
1028         static const unsigned bar_width = 20;
1029         static const unsigned bar_speed = 8;
1030 };
1032 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
1034         fd_set fds;
1036         FD_ZERO(&fds);
1038         int fd = card.fd();
1040         vector<unique_ptr<FlipState>> flipstates;
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;
1050                 string name;
1051                 for (const OutputInfo& o : outputs) {
1052                         name += to_string(o.connector->idx()) + ",";
1053                         ois.push_back(&o);
1054                 }
1056                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1057                 flipstates.push_back(move(fs));
1058         }
1060         for (unique_ptr<FlipState>& fs : flipstates)
1061                 fs->start_flipping();
1063         while (!max_flips_reached) {
1064                 int r;
1066                 FD_SET(0, &fds);
1067                 FD_SET(fd, &fds);
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         }
1082 int main(int argc, char **argv)
1084         vector<Arg> output_args = parse_cmdline(argc, argv);
1086         Card card(s_device_path);
1088         if (!card.has_atomic() && s_flip_sync)
1089                 EXIT("Synchronized flipping requires atomic modesetting");
1091         ResourceManager resman(card);
1093         vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1095         if (!s_flip_mode)
1096                 draw_test_patterns(outputs);
1098         print_outputs(outputs);
1100         set_crtcs_n_planes(card, outputs);
1102         printf("press enter to exit\n");
1104         if (s_flip_mode)
1105                 main_flip(card, outputs);
1106         else
1107                 getchar();