52e4614f9f49f8ea45b223aa520ef9de927aa269
[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>
8 #include <sys/select.h>
10 #include <kms++/kms++.h>
11 #include <kms++/modedb.h>
13 #include <kms++util/kms++util.h>
15 using namespace std;
16 using namespace kms;
18 struct PlaneInfo
19 {
20         Plane* plane;
22         unsigned x;
23         unsigned y;
24         unsigned w;
25         unsigned h;
27         vector<DumbFramebuffer*> fbs;
28 };
30 struct OutputInfo
31 {
32         Connector* connector;
34         Crtc* crtc;
35         Plane* primary_plane;
36         Videomode mode;
37         bool user_set_crtc;
38         vector<DumbFramebuffer*> fbs;
40         vector<PlaneInfo> planes;
41 };
43 static bool s_use_dmt;
44 static bool s_use_cea;
45 static unsigned s_num_buffers = 1;
46 static bool s_flip_mode;
47 static bool s_flip_sync;
49 static set<Crtc*> s_used_crtcs;
50 static set<Plane*> s_used_planes;
52 __attribute__ ((unused))
53 static void print_regex_match(smatch sm)
54 {
55         for (unsigned i = 0; i < sm.size(); ++i) {
56                 string str = sm[i].str();
57                 printf("%u: %s\n", i, str.c_str());
58         }
59 }
61 static void get_default_connector(Card& card, OutputInfo& output)
62 {
63         output.connector = card.get_first_connected_connector();
64         output.mode = output.connector->get_default_mode();
65 }
67 static void parse_connector(Card& card, const string& str, OutputInfo& output)
68 {
69         Connector* conn = resolve_connector(card, str);
71         if (!conn)
72                 EXIT("No connector '%s'", str.c_str());
74         if (!conn->connected())
75                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
77         output.connector = conn;
78         output.mode = output.connector->get_default_mode();
79 }
81 static void get_default_crtc(Card& card, OutputInfo& output)
82 {
83         Crtc* crtc = output.connector->get_current_crtc();
85         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
86                 s_used_crtcs.insert(crtc);
87                 output.crtc = crtc;
88                 return;
89         }
91         for (const auto& possible : output.connector->get_possible_crtcs()) {
92                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
93                         s_used_crtcs.insert(possible);
94                         output.crtc = possible;
95                         return;
96                 }
97         }
99         EXIT("Could not find available crtc");
102 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
104         // @12:1920x1200i@60
105         // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
107         const regex modename_re("(?:(@?)(\\d+):)?"      // @12:
108                                 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
109                                 "(?:@([\\d\\.]+))?");   // @60
111         const regex modeline_re("(?:(@?)(\\d+):)?"                      // @12:
112                                 "(\\d+),"                               // 33000000,
113                                 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-]),"   // 800/210/30/16/-,
114                                 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])"    // 480/22/13/10/-
115                                 "(?:,([i]+))?"                          // ,i
116                                 );
118         smatch sm;
119         if (regex_match(crtc_str, sm, modename_re)) {
120                 if (sm[2].matched) {
121                         bool use_id = sm[1].length() == 1;
122                         unsigned num = stoul(sm[2].str());
124                         if (use_id) {
125                                 Crtc* c = card.get_crtc(num);
126                                 if (!c)
127                                         EXIT("Bad crtc id '%u'", num);
129                                 output.crtc = c;
130                         } else {
131                                 auto crtcs = card.get_crtcs();
133                                 if (num >= crtcs.size())
134                                         EXIT("Bad crtc number '%u'", num);
136                                 output.crtc = crtcs[num];
137                         }
138                 } else {
139                         output.crtc = output.connector->get_current_crtc();
140                 }
142                 unsigned w = stoul(sm[3]);
143                 unsigned h = stoul(sm[4]);
144                 bool ilace = sm[5].matched ? true : false;
145                 float refresh = sm[6].matched ? stof(sm[6]) : 0;
147                 bool found_mode = false;
149                 try {
150                         output.mode = output.connector->get_mode(w, h, refresh, ilace);
151                         found_mode = true;
152                 } catch (exception& e) { }
154                 if (!found_mode && s_use_dmt) {
155                         try {
156                                 output.mode = find_dmt(w, h, refresh, ilace);
157                                 found_mode = true;
158                                 printf("Found mode from DMT\n");
159                         } catch (exception& e) { }
160                 }
162                 if (!found_mode && s_use_cea) {
163                         try {
164                                 output.mode = find_cea(w, h, refresh, ilace);
165                                 found_mode = true;
166                                 printf("Found mode from CEA\n");
167                         } catch (exception& e) { }
168                 }
170                 if (!found_mode)
171                         throw invalid_argument("Mode not found");
172         } else if (regex_match(crtc_str, sm, modeline_re)) {
173                 if (sm[2].matched) {
174                         bool use_id = sm[1].length() == 1;
175                         unsigned num = stoul(sm[2].str());
177                         if (use_id) {
178                                 Crtc* c = card.get_crtc(num);
179                                 if (!c)
180                                         EXIT("Bad crtc id '%u'", num);
182                                 output.crtc = c;
183                         } else {
184                                 auto crtcs = card.get_crtcs();
186                                 if (num >= crtcs.size())
187                                         EXIT("Bad crtc number '%u'", num);
189                                 output.crtc = crtcs[num];
190                         }
191                 } else {
192                         output.crtc = output.connector->get_current_crtc();
193                 }
195                 unsigned clock = stoul(sm[3]);
197                 unsigned hact = stoul(sm[4]);
198                 unsigned hfp = stoul(sm[5]);
199                 unsigned hsw = stoul(sm[6]);
200                 unsigned hbp = stoul(sm[7]);
201                 bool h_pos_sync = sm[8] == "+" ? true : false;
203                 unsigned vact = stoul(sm[9]);
204                 unsigned vfp = stoul(sm[10]);
205                 unsigned vsw = stoul(sm[11]);
206                 unsigned vbp = stoul(sm[12]);
207                 bool v_pos_sync = sm[13] == "+" ? true : false;
209                 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
210                 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
211                 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
213                 if (sm[14].matched) {
214                         for (int i = 0; i < sm[14].length(); ++i) {
215                                 char f = string(sm[14])[i];
217                                 switch (f) {
218                                 case 'i':
219                                         output.mode.set_interlace(true);
220                                         break;
221                                 default:
222                                         EXIT("Bad mode flag %c", f);
223                                 }
224                         }
225                 }
226         } else {
227                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
228         }
231 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
233         // 3:400,400-400x400
234         const regex plane_re("(?:(@?)(\\d+):)?"         // 3:
235                              "(?:(\\d+),(\\d+)-)?"      // 400,400-
236                              "(\\d+)x(\\d+)");          // 400x400
238         smatch sm;
239         if (!regex_match(plane_str, sm, plane_re))
240                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
242         if (sm[2].matched) {
243                 bool use_id = sm[1].length() == 1;
244                 unsigned num = stoul(sm[2].str());
246                 if (use_id) {
247                         Plane* p = card.get_plane(num);
248                         if (!p)
249                                 EXIT("Bad plane id '%u'", num);
251                         pinfo.plane = p;
252                 } else {
253                         auto planes = card.get_planes();
255                         if (num >= planes.size())
256                                 EXIT("Bad plane number '%u'", num);
258                         pinfo.plane = planes[num];
259                 }
260         } else {
261                 for (Plane* p : output.crtc->get_possible_planes()) {
262                         if (s_used_planes.find(p) != s_used_planes.end())
263                                 continue;
265                         if (p->plane_type() != PlaneType::Overlay)
266                                 continue;
268                         pinfo.plane = p;
269                 }
271                 if (!pinfo.plane)
272                         EXIT("Failed to find available plane");
273         }
275         s_used_planes.insert(pinfo.plane);
277         pinfo.w = stoul(sm[5]);
278         pinfo.h = stoul(sm[6]);
280         if (sm[3].matched)
281                 pinfo.x = stoul(sm[3]);
282         else
283                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
285         if (sm[4].matched)
286                 pinfo.y = stoul(sm[4]);
287         else
288                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
291 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
293         vector<DumbFramebuffer*> v;
295         for (unsigned i = 0; i < s_num_buffers; ++i)
296                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
298         return v;
301 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
303         unsigned w = def_w;
304         unsigned h = def_h;
305         PixelFormat format = PixelFormat::XRGB8888;
307         if (!fb_str.empty()) {
308                 // XXX the regexp is not quite correct
309                 // 400x400-NV12
310                 const regex fb_re("(?:(\\d+)x(\\d+))?"          // 400x400
311                                   "(?:-)?"                      // -
312                                   "(\\w\\w\\w\\w)?");           // NV12
314                 smatch sm;
315                 if (!regex_match(fb_str, sm, fb_re))
316                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
318                 if (sm[1].matched)
319                         w = stoul(sm[1]);
320                 if (sm[2].matched)
321                         h = stoul(sm[2]);
322                 if (sm[3].matched)
323                         format = FourCCToPixelFormat(sm[3]);
324         }
326         vector<DumbFramebuffer*> v;
328         for (unsigned i = 0; i < s_num_buffers; ++i)
329                 v.push_back(new DumbFramebuffer(card, w, h, format));
331         return v;
334 static const char* usage_str =
335                 "Usage: kmstest [OPTION]...\n\n"
336                 "Show a test pattern on a display or plane\n\n"
337                 "Options:\n"
338                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
339                 "  -c, --connector=CONN      CONN is <connector>\n"
340                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
341                 "                            or\n"
342                 "                            [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
343                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
344                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
345                 "      --dmt                 Search for the given mode from DMT tables\n"
346                 "      --cea                 Search for the given mode from CEA tables\n"
347                 "      --flip                Do page flipping for each output\n"
348                 "      --sync                Synchronize page flipping\n"
349                 "\n"
350                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
351                 "<connector> can also be given by name.\n"
352                 "\n"
353                 "Options can be given multiple times to set up multiple displays or planes.\n"
354                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
355                 "an earlier option.\n"
356                 "If you omit parameters, kmstest tries to guess what you mean\n"
357                 "\n"
358                 "Examples:\n"
359                 "\n"
360                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
361                 "    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
362                 "XR24 framebuffer on first connected connector in the default mode:\n"
363                 "    kmstest -f XR24\n\n"
364                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
365                 "    kmstest -p 400x400 -f XR24\n\n"
366                 "Test pattern on the second connector with default mode:\n"
367                 "    kmstest -c 1\n"
368                 ;
370 static void usage()
372         puts(usage_str);
375 enum class ObjectType
377         Connector,
378         Crtc,
379         Plane,
380         Framebuffer,
381 };
383 struct Arg
385         ObjectType type;
386         string arg;
387 };
389 static string s_device_path = "/dev/dri/card0";
391 static vector<Arg> parse_cmdline(int argc, char **argv)
393         vector<Arg> args;
395         OptionSet optionset = {
396                 Option("|device=",
397                 [&](string s)
398                 {
399                         s_device_path = s;
400                 }),
401                 Option("c|connector=",
402                 [&](string s)
403                 {
404                         args.push_back(Arg { ObjectType::Connector, s });
405                 }),
406                 Option("r|crtc=", [&](string s)
407                 {
408                         args.push_back(Arg { ObjectType::Crtc, s });
409                 }),
410                 Option("p|plane=", [&](string s)
411                 {
412                         args.push_back(Arg { ObjectType::Plane, s });
413                 }),
414                 Option("f|fb=", [&](string s)
415                 {
416                         args.push_back(Arg { ObjectType::Framebuffer, s });
417                 }),
418                 Option("|dmt", []()
419                 {
420                         s_use_dmt = true;
421                 }),
422                 Option("|cea", []()
423                 {
424                         s_use_cea = true;
425                 }),
426                 Option("|flip", []()
427                 {
428                         s_flip_mode = true;
429                         s_num_buffers = 2;
430                 }),
431                 Option("|sync", []()
432                 {
433                         s_flip_sync = true;
434                 }),
435                 Option("h|help", [&]()
436                 {
437                         usage();
438                         exit(-1);
439                 }),
440         };
442         optionset.parse(argc, argv);
444         if (optionset.params().size() > 0) {
445                 usage();
446                 exit(-1);
447         }
449         return args;
452 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
454         vector<OutputInfo> outputs;
456         if (output_args.size() == 0) {
457                 // no output args, show a pattern on all screens
458                 for (auto& pipe : card.get_connected_pipelines()) {
459                         OutputInfo output = { };
460                         output.connector = pipe.connector;
461                         output.crtc = pipe.crtc;
462                         output.mode = output.connector->get_default_mode();
464                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
466                         outputs.push_back(output);
467                 }
469                 return outputs;
470         }
472         OutputInfo* current_output = 0;
473         PlaneInfo* current_plane = 0;
475         for (auto& arg : output_args) {
476                 switch (arg.type) {
477                 case ObjectType::Connector:
478                 {
479                         outputs.push_back(OutputInfo { });
480                         current_output = &outputs.back();
482                         parse_connector(card, arg.arg, *current_output);
483                         current_plane = 0;
485                         break;
486                 }
488                 case ObjectType::Crtc:
489                 {
490                         if (!current_output) {
491                                 outputs.push_back(OutputInfo { });
492                                 current_output = &outputs.back();
493                         }
495                         if (!current_output->connector)
496                                 get_default_connector(card, *current_output);
498                         parse_crtc(card, arg.arg, *current_output);
500                         current_output->user_set_crtc = true;
502                         current_plane = 0;
504                         break;
505                 }
507                 case ObjectType::Plane:
508                 {
509                         if (!current_output) {
510                                 outputs.push_back(OutputInfo { });
511                                 current_output = &outputs.back();
512                         }
514                         if (!current_output->connector)
515                                 get_default_connector(card, *current_output);
517                         if (!current_output->crtc)
518                                 get_default_crtc(card, *current_output);
520                         current_output->planes.push_back(PlaneInfo { });
521                         current_plane = &current_output->planes.back();
523                         parse_plane(card, arg.arg, *current_output, *current_plane);
525                         break;
526                 }
528                 case ObjectType::Framebuffer:
529                 {
530                         if (!current_output) {
531                                 outputs.push_back(OutputInfo { });
532                                 current_output = &outputs.back();
533                         }
535                         if (!current_output->connector)
536                                 get_default_connector(card, *current_output);
538                         if (!current_output->crtc)
539                                 get_default_crtc(card, *current_output);
541                         int def_w, def_h;
543                         if (current_plane) {
544                                 def_w = current_plane->w;
545                                 def_h = current_plane->h;
546                         } else {
547                                 def_w = current_output->mode.hdisplay;
548                                 def_h = current_output->mode.vdisplay;
549                         }
551                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
553                         if (current_plane)
554                                 current_plane->fbs = fbs;
555                         else
556                                 current_output->fbs = fbs;
558                         break;
559                 }
560                 }
561         }
563         // create default framebuffers if needed
564         for (OutputInfo& o : outputs) {
565                 if (!o.crtc) {
566                         get_default_crtc(card, *current_output);
567                         o.user_set_crtc = true;
568                 }
570                 if (o.fbs.empty() && o.user_set_crtc)
571                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
573                 for (PlaneInfo &p : o.planes) {
574                         if (p.fbs.empty())
575                                 p.fbs = get_default_fb(card, p.w, p.h);
576                 }
577         }
579         return outputs;
582 static std::string videomode_to_string(const Videomode& m)
584         string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
585         string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
587         return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
588                        m.name.c_str(),
589                        m.clock / 1000.0,
590                        h.c_str(), v.c_str(),
591                        m.vrefresh, m.calculated_vrefresh(),
592                        m.flags,
593                        m.type);
596 static void print_outputs(const vector<OutputInfo>& outputs)
598         for (unsigned i = 0; i < outputs.size(); ++i) {
599                 const OutputInfo& o = outputs[i];
601                 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
602                        o.connector->fullname().c_str());
603                 printf("  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
604                 if (o.primary_plane)
605                         printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
606                 printf(": %s\n", videomode_to_string(o.mode).c_str());
607                 if (!o.fbs.empty()) {
608                         auto fb = o.fbs[0];
609                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
610                                PixelFormatToFourCC(fb->format()).c_str());
611                 }
613                 for (unsigned j = 0; j < o.planes.size(); ++j) {
614                         const PlaneInfo& p = o.planes[j];
615                         auto fb = p.fbs[0];
616                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
617                                p.x, p.y, p.w, p.h);
618                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
619                                PixelFormatToFourCC(fb->format()).c_str());
620                 }
621         }
624 static void draw_test_patterns(const vector<OutputInfo>& outputs)
626         for (const OutputInfo& o : outputs) {
627                 for (auto fb : o.fbs)
628                         draw_test_pattern(*fb);
630                 for (const PlaneInfo& p : o.planes)
631                         for (auto fb : p.fbs)
632                                 draw_test_pattern(*fb);
633         }
636 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
638         for (const OutputInfo& o : outputs) {
639                 auto conn = o.connector;
640                 auto crtc = o.crtc;
642                 if (!o.fbs.empty()) {
643                         auto fb = o.fbs[0];
644                         int r = crtc->set_mode(conn, *fb, o.mode);
645                         if (r)
646                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
647                                        crtc->id(), strerror(-r));
648                 }
650                 for (const PlaneInfo& p : o.planes) {
651                         auto fb = p.fbs[0];
652                         int r = crtc->set_plane(p.plane, *fb,
653                                                 p.x, p.y, p.w, p.h,
654                                                 0, 0, fb->width(), fb->height());
655                         if (r)
656                                 printf("crtc->set_plane() failed for plane %u: %s\n",
657                                        p.plane->id(), strerror(-r));
658                 }
659         }
662 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
664         // Keep blobs here so that we keep ref to them until we have committed the req
665         vector<unique_ptr<Blob>> blobs;
667         AtomicReq req(card);
669         for (const OutputInfo& o : outputs) {
670                 auto conn = o.connector;
671                 auto crtc = o.crtc;
673                 blobs.emplace_back(o.mode.to_blob(card));
674                 Blob* mode_blob = blobs.back().get();
676                 req.add(conn, {
677                                 { "CRTC_ID", crtc->id() },
678                         });
680                 req.add(crtc, {
681                                 { "ACTIVE", 1 },
682                                 { "MODE_ID", mode_blob->id() },
683                         });
685                 if (!o.fbs.empty()) {
686                         auto fb = o.fbs[0];
688                         req.add(o.primary_plane, {
689                                         { "FB_ID", fb->id() },
690                                         { "CRTC_ID", crtc->id() },
691                                         { "SRC_X", 0 << 16 },
692                                         { "SRC_Y", 0 << 16 },
693                                         { "SRC_W", fb->width() << 16 },
694                                         { "SRC_H", fb->height() << 16 },
695                                         { "CRTC_X", 0 },
696                                         { "CRTC_Y", 0 },
697                                         { "CRTC_W", fb->width() },
698                                         { "CRTC_H", fb->height() },
699                                 });
700                 }
702                 for (const PlaneInfo& p : o.planes) {
703                         auto fb = p.fbs[0];
705                         req.add(p.plane, {
706                                         { "FB_ID", fb->id() },
707                                         { "CRTC_ID", crtc->id() },
708                                         { "SRC_X", 0 << 16 },
709                                         { "SRC_Y", 0 << 16 },
710                                         { "SRC_W", fb->width() << 16 },
711                                         { "SRC_H", fb->height() << 16 },
712                                         { "CRTC_X", p.x },
713                                         { "CRTC_Y", p.y },
714                                         { "CRTC_W", p.w },
715                                         { "CRTC_H", p.h },
716                                 });
717                 }
718         }
720         int r;
722         r = req.test(true);
723         if (r)
724                 EXIT("Atomic test failed: %d\n", r);
726         r = req.commit_sync(true);
727         if (r)
728                 EXIT("Atomic commit failed: %d\n", r);
731 class FlipState : private PageFlipHandlerBase
733 public:
734         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
735                 : m_card(card), m_name(name), m_outputs(outputs)
736         {
737         }
739         void start_flipping()
740         {
741                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
742                 m_slowest_frame = std::chrono::duration<float>::min();
743                 m_frame_num = 0;
744                 queue_next();
745         }
747 private:
748         void handle_page_flip(uint32_t frame, double time)
749         {
750                 m_frame_num++;
752                 auto now = std::chrono::steady_clock::now();
754                 std::chrono::duration<float> diff = now - m_prev_frame;
755                 if (diff > m_slowest_frame)
756                         m_slowest_frame = diff;
758                 if (m_frame_num  % 100 == 0) {
759                         std::chrono::duration<float> fsec = now - m_prev_print;
760                         printf("Connector %s: fps %f, slowest %.2f ms\n",
761                                m_name.c_str(),
762                                100.0 / fsec.count(),
763                                m_slowest_frame.count() * 1000);
764                         m_prev_print = now;
765                         m_slowest_frame = std::chrono::duration<float>::min();
766                 }
768                 m_prev_frame = now;
770                 queue_next();
771         }
773         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
774         {
775                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
776         }
778         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
779         {
780                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
781                 int new_xpos = get_bar_pos(fb, frame_num);
783                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
784                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
785         }
787         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
788         {
789                 unsigned cur = frame_num % s_num_buffers;
791                 if (!o.fbs.empty()) {
792                         auto fb = o.fbs[cur];
794                         draw_bar(fb, frame_num);
796                         req.add(o.primary_plane, {
797                                         { "FB_ID", fb->id() },
798                                 });
799                 }
801                 for (const PlaneInfo& p : o.planes) {
802                         auto fb = p.fbs[cur];
804                         draw_bar(fb, frame_num);
806                         req.add(p.plane, {
807                                         { "FB_ID", fb->id() },
808                                 });
809                 }
810         }
812         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
813         {
814                 unsigned cur = frame_num % s_num_buffers;
816                 if (!o.fbs.empty()) {
817                         auto fb = o.fbs[cur];
819                         draw_bar(fb, frame_num);
821                         int r = o.crtc->page_flip(*fb, this);
822                         ASSERT(r == 0);
823                 }
825                 for (const PlaneInfo& p : o.planes) {
826                         auto fb = p.fbs[cur];
828                         draw_bar(fb, frame_num);
830                         int r = o.crtc->set_plane(p.plane, *fb,
831                                                   p.x, p.y, p.w, p.h,
832                                                   0, 0, fb->width(), fb->height());
833                         ASSERT(r == 0);
834                 }
835         }
837         void queue_next()
838         {
839                 if (m_card.has_atomic()) {
840                         AtomicReq req(m_card);
842                         for (auto o : m_outputs)
843                                 do_flip_output(req, m_frame_num, *o);
845                         int r = req.commit(this);
846                         if (r)
847                                 EXIT("Flip commit failed: %d\n", r);
848                 } else {
849                         ASSERT(m_outputs.size() == 1);
850                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
851                 }
852         }
854         Card& m_card;
855         string m_name;
856         vector<const OutputInfo*> m_outputs;
857         unsigned m_frame_num;
859         chrono::steady_clock::time_point m_prev_print;
860         chrono::steady_clock::time_point m_prev_frame;
861         chrono::duration<float> m_slowest_frame;
863         static const unsigned bar_width = 20;
864         static const unsigned bar_speed = 8;
865 };
867 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
869         fd_set fds;
871         FD_ZERO(&fds);
873         int fd = card.fd();
875         vector<unique_ptr<FlipState>> flipstates;
877         if (!s_flip_sync) {
878                 for (const OutputInfo& o : outputs) {
879                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
880                         flipstates.push_back(move(fs));
881                 }
882         } else {
883                 vector<const OutputInfo*> ois;
885                 string name;
886                 for (const OutputInfo& o : outputs) {
887                         name += to_string(o.connector->idx()) + ",";
888                         ois.push_back(&o);
889                 }
891                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
892                 flipstates.push_back(move(fs));
893         }
895         for (unique_ptr<FlipState>& fs : flipstates)
896                 fs->start_flipping();
898         while (true) {
899                 int r;
901                 FD_SET(0, &fds);
902                 FD_SET(fd, &fds);
904                 r = select(fd + 1, &fds, NULL, NULL, NULL);
905                 if (r < 0) {
906                         fprintf(stderr, "select() failed with %d: %m\n", errno);
907                         break;
908                 } else if (FD_ISSET(0, &fds)) {
909                         fprintf(stderr, "Exit due to user-input\n");
910                         break;
911                 } else if (FD_ISSET(fd, &fds)) {
912                         card.call_page_flip_handlers();
913                 }
914         }
917 int main(int argc, char **argv)
919         vector<Arg> output_args = parse_cmdline(argc, argv);
921         Card card(s_device_path);
923         if (!card.has_atomic() && s_flip_sync)
924                 EXIT("Synchronized flipping requires atomic modesetting");
926         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
928         if (card.has_atomic()) {
929                 for (OutputInfo& o : outputs) {
930                         o.primary_plane = o.crtc->get_primary_plane();
932                         if (!o.fbs.empty() && !o.primary_plane)
933                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
934                 }
935         }
937         if (!s_flip_mode)
938                 draw_test_patterns(outputs);
940         print_outputs(outputs);
942         if (card.has_atomic())
943                 set_crtcs_n_planes(card, outputs);
944         else
945                 set_crtcs_n_planes_legacy(card, outputs);
947         printf("press enter to exit\n");
949         if (s_flip_mode)
950                 main_flip(card, outputs);
951         else
952                 getchar();