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