modedb_dmt: update table
[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>
12 #include <kms++/mode_cvt.h>
14 #include <kms++util/kms++util.h>
16 using namespace std;
17 using namespace kms;
19 struct PlaneInfo
20 {
21         Plane* plane;
23         unsigned x;
24         unsigned y;
25         unsigned w;
26         unsigned h;
28         vector<DumbFramebuffer*> fbs;
29 };
31 struct OutputInfo
32 {
33         Connector* connector;
35         Crtc* crtc;
36         Plane* primary_plane;
37         Videomode mode;
38         bool user_set_crtc;
39         vector<DumbFramebuffer*> fbs;
41         vector<PlaneInfo> planes;
42 };
44 static bool s_use_dmt;
45 static bool s_use_cea;
46 static unsigned s_num_buffers = 1;
47 static bool s_flip_mode;
48 static bool s_flip_sync;
49 static bool s_cvt;
50 static bool s_cvt_v2;
51 static bool s_cvt_vid_opt;
53 static set<Crtc*> s_used_crtcs;
54 static set<Plane*> s_used_planes;
56 __attribute__ ((unused))
57 static void print_regex_match(smatch sm)
58 {
59         for (unsigned i = 0; i < sm.size(); ++i) {
60                 string str = sm[i].str();
61                 printf("%u: %s\n", i, str.c_str());
62         }
63 }
65 static void get_default_connector(Card& card, OutputInfo& output)
66 {
67         output.connector = card.get_first_connected_connector();
68         output.mode = output.connector->get_default_mode();
69 }
71 static void parse_connector(Card& card, const string& str, OutputInfo& output)
72 {
73         Connector* conn = resolve_connector(card, str);
75         if (!conn)
76                 EXIT("No connector '%s'", str.c_str());
78         if (!conn->connected())
79                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
81         output.connector = conn;
82         output.mode = output.connector->get_default_mode();
83 }
85 static void get_default_crtc(Card& card, OutputInfo& output)
86 {
87         Crtc* crtc = output.connector->get_current_crtc();
89         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
90                 s_used_crtcs.insert(crtc);
91                 output.crtc = crtc;
92                 return;
93         }
95         for (const auto& possible : output.connector->get_possible_crtcs()) {
96                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
97                         s_used_crtcs.insert(possible);
98                         output.crtc = possible;
99                         return;
100                 }
101         }
103         EXIT("Could not find available crtc");
106 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
108         // @12:1920x1200i@60
109         // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
111         const regex modename_re("(?:(@?)(\\d+):)?"      // @12:
112                                 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
113                                 "(?:@([\\d\\.]+))?");   // @60
115         const regex modeline_re("(?:(@?)(\\d+):)?"                      // @12:
116                                 "(\\d+),"                               // 33000000,
117                                 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-]),"   // 800/210/30/16/-,
118                                 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])"    // 480/22/13/10/-
119                                 "(?:,([i]+))?"                          // ,i
120                                 );
122         smatch sm;
123         if (regex_match(crtc_str, sm, modename_re)) {
124                 if (sm[2].matched) {
125                         bool use_id = sm[1].length() == 1;
126                         unsigned num = stoul(sm[2].str());
128                         if (use_id) {
129                                 Crtc* c = card.get_crtc(num);
130                                 if (!c)
131                                         EXIT("Bad crtc id '%u'", num);
133                                 output.crtc = c;
134                         } else {
135                                 auto crtcs = card.get_crtcs();
137                                 if (num >= crtcs.size())
138                                         EXIT("Bad crtc number '%u'", num);
140                                 output.crtc = crtcs[num];
141                         }
142                 } else {
143                         output.crtc = output.connector->get_current_crtc();
144                 }
146                 unsigned w = stoul(sm[3]);
147                 unsigned h = stoul(sm[4]);
148                 bool ilace = sm[5].matched ? true : false;
149                 float refresh = sm[6].matched ? stof(sm[6]) : 0;
151                 if (s_cvt) {
152                         output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
153                 } else if (s_use_dmt) {
154                         try {
155                                 output.mode = find_dmt(w, h, refresh, ilace);
156                         } catch (exception& e) {
157                                 EXIT("Mode not found from DMT tables\n");
158                         }
159                 } else if (s_use_cea) {
160                         try {
161                                 output.mode = find_cea(w, h, refresh, ilace);
162                         } catch (exception& e) {
163                                 EXIT("Mode not found from CEA tables\n");
164                         }
165                 } else {
166                         try {
167                                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
168                         } catch (exception& e) {
169                                 EXIT("Mode not found from the connector\n");
170                         }
171                 }
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                 "      --cvt=CVT             Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
348                 "      --flip                Do page flipping for each output\n"
349                 "      --sync                Synchronize page flipping\n"
350                 "\n"
351                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
352                 "<connector> can also be given by name.\n"
353                 "\n"
354                 "Options can be given multiple times to set up multiple displays or planes.\n"
355                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
356                 "an earlier option.\n"
357                 "If you omit parameters, kmstest tries to guess what you mean\n"
358                 "\n"
359                 "Examples:\n"
360                 "\n"
361                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
362                 "    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
363                 "XR24 framebuffer on first connected connector in the default mode:\n"
364                 "    kmstest -f XR24\n\n"
365                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
366                 "    kmstest -p 400x400 -f XR24\n\n"
367                 "Test pattern on the second connector with default mode:\n"
368                 "    kmstest -c 1\n"
369                 ;
371 static void usage()
373         puts(usage_str);
376 enum class ObjectType
378         Connector,
379         Crtc,
380         Plane,
381         Framebuffer,
382 };
384 struct Arg
386         ObjectType type;
387         string arg;
388 };
390 static string s_device_path = "/dev/dri/card0";
392 static vector<Arg> parse_cmdline(int argc, char **argv)
394         vector<Arg> args;
396         OptionSet optionset = {
397                 Option("|device=",
398                 [&](string s)
399                 {
400                         s_device_path = s;
401                 }),
402                 Option("c|connector=",
403                 [&](string s)
404                 {
405                         args.push_back(Arg { ObjectType::Connector, s });
406                 }),
407                 Option("r|crtc=", [&](string s)
408                 {
409                         args.push_back(Arg { ObjectType::Crtc, s });
410                 }),
411                 Option("p|plane=", [&](string s)
412                 {
413                         args.push_back(Arg { ObjectType::Plane, s });
414                 }),
415                 Option("f|fb=", [&](string s)
416                 {
417                         args.push_back(Arg { ObjectType::Framebuffer, s });
418                 }),
419                 Option("|dmt", []()
420                 {
421                         s_use_dmt = true;
422                 }),
423                 Option("|cea", []()
424                 {
425                         s_use_cea = true;
426                 }),
427                 Option("|flip", []()
428                 {
429                         s_flip_mode = true;
430                         s_num_buffers = 2;
431                 }),
432                 Option("|sync", []()
433                 {
434                         s_flip_sync = true;
435                 }),
436                 Option("|cvt=", [&](string s)
437                 {
438                         if (s == "v1")
439                                 s_cvt = true;
440                         else if (s == "v2")
441                                 s_cvt = s_cvt_v2 = true;
442                         else if (s == "v2o")
443                                 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
444                         else {
445                                 usage();
446                                 exit(-1);
447                         }
448                 }),
449                 Option("h|help", [&]()
450                 {
451                         usage();
452                         exit(-1);
453                 }),
454         };
456         optionset.parse(argc, argv);
458         if (optionset.params().size() > 0) {
459                 usage();
460                 exit(-1);
461         }
463         return args;
466 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
468         vector<OutputInfo> outputs;
470         if (output_args.size() == 0) {
471                 // no output args, show a pattern on all screens
472                 for (auto& pipe : card.get_connected_pipelines()) {
473                         OutputInfo output = { };
474                         output.connector = pipe.connector;
475                         output.crtc = pipe.crtc;
476                         output.mode = output.connector->get_default_mode();
478                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
480                         outputs.push_back(output);
481                 }
483                 return outputs;
484         }
486         OutputInfo* current_output = 0;
487         PlaneInfo* current_plane = 0;
489         for (auto& arg : output_args) {
490                 switch (arg.type) {
491                 case ObjectType::Connector:
492                 {
493                         outputs.push_back(OutputInfo { });
494                         current_output = &outputs.back();
496                         parse_connector(card, arg.arg, *current_output);
497                         current_plane = 0;
499                         break;
500                 }
502                 case ObjectType::Crtc:
503                 {
504                         if (!current_output) {
505                                 outputs.push_back(OutputInfo { });
506                                 current_output = &outputs.back();
507                         }
509                         if (!current_output->connector)
510                                 get_default_connector(card, *current_output);
512                         parse_crtc(card, arg.arg, *current_output);
514                         current_output->user_set_crtc = true;
516                         current_plane = 0;
518                         break;
519                 }
521                 case ObjectType::Plane:
522                 {
523                         if (!current_output) {
524                                 outputs.push_back(OutputInfo { });
525                                 current_output = &outputs.back();
526                         }
528                         if (!current_output->connector)
529                                 get_default_connector(card, *current_output);
531                         if (!current_output->crtc)
532                                 get_default_crtc(card, *current_output);
534                         current_output->planes.push_back(PlaneInfo { });
535                         current_plane = &current_output->planes.back();
537                         parse_plane(card, arg.arg, *current_output, *current_plane);
539                         break;
540                 }
542                 case ObjectType::Framebuffer:
543                 {
544                         if (!current_output) {
545                                 outputs.push_back(OutputInfo { });
546                                 current_output = &outputs.back();
547                         }
549                         if (!current_output->connector)
550                                 get_default_connector(card, *current_output);
552                         if (!current_output->crtc)
553                                 get_default_crtc(card, *current_output);
555                         int def_w, def_h;
557                         if (current_plane) {
558                                 def_w = current_plane->w;
559                                 def_h = current_plane->h;
560                         } else {
561                                 def_w = current_output->mode.hdisplay;
562                                 def_h = current_output->mode.vdisplay;
563                         }
565                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
567                         if (current_plane)
568                                 current_plane->fbs = fbs;
569                         else
570                                 current_output->fbs = fbs;
572                         break;
573                 }
574                 }
575         }
577         // create default framebuffers if needed
578         for (OutputInfo& o : outputs) {
579                 if (!o.crtc) {
580                         get_default_crtc(card, *current_output);
581                         o.user_set_crtc = true;
582                 }
584                 if (o.fbs.empty() && o.user_set_crtc)
585                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
587                 for (PlaneInfo &p : o.planes) {
588                         if (p.fbs.empty())
589                                 p.fbs = get_default_fb(card, p.w, p.h);
590                 }
591         }
593         return outputs;
596 static std::string videomode_to_string(const Videomode& m)
598         string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
599         string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
601         return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
602                        m.name.c_str(),
603                        m.clock / 1000.0,
604                        h.c_str(), v.c_str(),
605                        m.vrefresh, m.calculated_vrefresh(),
606                        m.flags,
607                        m.type);
610 static void print_outputs(const vector<OutputInfo>& outputs)
612         for (unsigned i = 0; i < outputs.size(); ++i) {
613                 const OutputInfo& o = outputs[i];
615                 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
616                        o.connector->fullname().c_str());
617                 printf("  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
618                 if (o.primary_plane)
619                         printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
620                 printf(": %s\n", videomode_to_string(o.mode).c_str());
621                 if (!o.fbs.empty()) {
622                         auto fb = o.fbs[0];
623                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
624                                PixelFormatToFourCC(fb->format()).c_str());
625                 }
627                 for (unsigned j = 0; j < o.planes.size(); ++j) {
628                         const PlaneInfo& p = o.planes[j];
629                         auto fb = p.fbs[0];
630                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
631                                p.x, p.y, p.w, p.h);
632                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
633                                PixelFormatToFourCC(fb->format()).c_str());
634                 }
635         }
638 static void draw_test_patterns(const vector<OutputInfo>& outputs)
640         for (const OutputInfo& o : outputs) {
641                 for (auto fb : o.fbs)
642                         draw_test_pattern(*fb);
644                 for (const PlaneInfo& p : o.planes)
645                         for (auto fb : p.fbs)
646                                 draw_test_pattern(*fb);
647         }
650 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
652         for (const OutputInfo& o : outputs) {
653                 auto conn = o.connector;
654                 auto crtc = o.crtc;
656                 if (!o.fbs.empty()) {
657                         auto fb = o.fbs[0];
658                         int r = crtc->set_mode(conn, *fb, o.mode);
659                         if (r)
660                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
661                                        crtc->id(), strerror(-r));
662                 }
664                 for (const PlaneInfo& p : o.planes) {
665                         auto fb = p.fbs[0];
666                         int r = crtc->set_plane(p.plane, *fb,
667                                                 p.x, p.y, p.w, p.h,
668                                                 0, 0, fb->width(), fb->height());
669                         if (r)
670                                 printf("crtc->set_plane() failed for plane %u: %s\n",
671                                        p.plane->id(), strerror(-r));
672                 }
673         }
676 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
678         // Keep blobs here so that we keep ref to them until we have committed the req
679         vector<unique_ptr<Blob>> blobs;
681         AtomicReq req(card);
683         for (const OutputInfo& o : outputs) {
684                 auto conn = o.connector;
685                 auto crtc = o.crtc;
687                 blobs.emplace_back(o.mode.to_blob(card));
688                 Blob* mode_blob = blobs.back().get();
690                 req.add(conn, {
691                                 { "CRTC_ID", crtc->id() },
692                         });
694                 req.add(crtc, {
695                                 { "ACTIVE", 1 },
696                                 { "MODE_ID", mode_blob->id() },
697                         });
699                 if (!o.fbs.empty()) {
700                         auto fb = o.fbs[0];
702                         req.add(o.primary_plane, {
703                                         { "FB_ID", fb->id() },
704                                         { "CRTC_ID", crtc->id() },
705                                         { "SRC_X", 0 << 16 },
706                                         { "SRC_Y", 0 << 16 },
707                                         { "SRC_W", fb->width() << 16 },
708                                         { "SRC_H", fb->height() << 16 },
709                                         { "CRTC_X", 0 },
710                                         { "CRTC_Y", 0 },
711                                         { "CRTC_W", fb->width() },
712                                         { "CRTC_H", fb->height() },
713                                 });
714                 }
716                 for (const PlaneInfo& p : o.planes) {
717                         auto fb = p.fbs[0];
719                         req.add(p.plane, {
720                                         { "FB_ID", fb->id() },
721                                         { "CRTC_ID", crtc->id() },
722                                         { "SRC_X", 0 << 16 },
723                                         { "SRC_Y", 0 << 16 },
724                                         { "SRC_W", fb->width() << 16 },
725                                         { "SRC_H", fb->height() << 16 },
726                                         { "CRTC_X", p.x },
727                                         { "CRTC_Y", p.y },
728                                         { "CRTC_W", p.w },
729                                         { "CRTC_H", p.h },
730                                 });
731                 }
732         }
734         int r;
736         r = req.test(true);
737         if (r)
738                 EXIT("Atomic test failed: %d\n", r);
740         r = req.commit_sync(true);
741         if (r)
742                 EXIT("Atomic commit failed: %d\n", r);
745 class FlipState : private PageFlipHandlerBase
747 public:
748         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
749                 : m_card(card), m_name(name), m_outputs(outputs)
750         {
751         }
753         void start_flipping()
754         {
755                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
756                 m_slowest_frame = std::chrono::duration<float>::min();
757                 m_frame_num = 0;
758                 queue_next();
759         }
761 private:
762         void handle_page_flip(uint32_t frame, double time)
763         {
764                 m_frame_num++;
766                 auto now = std::chrono::steady_clock::now();
768                 std::chrono::duration<float> diff = now - m_prev_frame;
769                 if (diff > m_slowest_frame)
770                         m_slowest_frame = diff;
772                 if (m_frame_num  % 100 == 0) {
773                         std::chrono::duration<float> fsec = now - m_prev_print;
774                         printf("Connector %s: fps %f, slowest %.2f ms\n",
775                                m_name.c_str(),
776                                100.0 / fsec.count(),
777                                m_slowest_frame.count() * 1000);
778                         m_prev_print = now;
779                         m_slowest_frame = std::chrono::duration<float>::min();
780                 }
782                 m_prev_frame = now;
784                 queue_next();
785         }
787         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
788         {
789                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
790         }
792         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
793         {
794                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
795                 int new_xpos = get_bar_pos(fb, frame_num);
797                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
798                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
799         }
801         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
802         {
803                 unsigned cur = frame_num % s_num_buffers;
805                 if (!o.fbs.empty()) {
806                         auto fb = o.fbs[cur];
808                         draw_bar(fb, frame_num);
810                         req.add(o.primary_plane, {
811                                         { "FB_ID", fb->id() },
812                                 });
813                 }
815                 for (const PlaneInfo& p : o.planes) {
816                         auto fb = p.fbs[cur];
818                         draw_bar(fb, frame_num);
820                         req.add(p.plane, {
821                                         { "FB_ID", fb->id() },
822                                 });
823                 }
824         }
826         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
827         {
828                 unsigned cur = frame_num % s_num_buffers;
830                 if (!o.fbs.empty()) {
831                         auto fb = o.fbs[cur];
833                         draw_bar(fb, frame_num);
835                         int r = o.crtc->page_flip(*fb, this);
836                         ASSERT(r == 0);
837                 }
839                 for (const PlaneInfo& p : o.planes) {
840                         auto fb = p.fbs[cur];
842                         draw_bar(fb, frame_num);
844                         int r = o.crtc->set_plane(p.plane, *fb,
845                                                   p.x, p.y, p.w, p.h,
846                                                   0, 0, fb->width(), fb->height());
847                         ASSERT(r == 0);
848                 }
849         }
851         void queue_next()
852         {
853                 if (m_card.has_atomic()) {
854                         AtomicReq req(m_card);
856                         for (auto o : m_outputs)
857                                 do_flip_output(req, m_frame_num, *o);
859                         int r = req.commit(this);
860                         if (r)
861                                 EXIT("Flip commit failed: %d\n", r);
862                 } else {
863                         ASSERT(m_outputs.size() == 1);
864                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
865                 }
866         }
868         Card& m_card;
869         string m_name;
870         vector<const OutputInfo*> m_outputs;
871         unsigned m_frame_num;
873         chrono::steady_clock::time_point m_prev_print;
874         chrono::steady_clock::time_point m_prev_frame;
875         chrono::duration<float> m_slowest_frame;
877         static const unsigned bar_width = 20;
878         static const unsigned bar_speed = 8;
879 };
881 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
883         fd_set fds;
885         FD_ZERO(&fds);
887         int fd = card.fd();
889         vector<unique_ptr<FlipState>> flipstates;
891         if (!s_flip_sync) {
892                 for (const OutputInfo& o : outputs) {
893                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
894                         flipstates.push_back(move(fs));
895                 }
896         } else {
897                 vector<const OutputInfo*> ois;
899                 string name;
900                 for (const OutputInfo& o : outputs) {
901                         name += to_string(o.connector->idx()) + ",";
902                         ois.push_back(&o);
903                 }
905                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
906                 flipstates.push_back(move(fs));
907         }
909         for (unique_ptr<FlipState>& fs : flipstates)
910                 fs->start_flipping();
912         while (true) {
913                 int r;
915                 FD_SET(0, &fds);
916                 FD_SET(fd, &fds);
918                 r = select(fd + 1, &fds, NULL, NULL, NULL);
919                 if (r < 0) {
920                         fprintf(stderr, "select() failed with %d: %m\n", errno);
921                         break;
922                 } else if (FD_ISSET(0, &fds)) {
923                         fprintf(stderr, "Exit due to user-input\n");
924                         break;
925                 } else if (FD_ISSET(fd, &fds)) {
926                         card.call_page_flip_handlers();
927                 }
928         }
931 int main(int argc, char **argv)
933         vector<Arg> output_args = parse_cmdline(argc, argv);
935         Card card(s_device_path);
937         if (!card.has_atomic() && s_flip_sync)
938                 EXIT("Synchronized flipping requires atomic modesetting");
940         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
942         if (card.has_atomic()) {
943                 for (OutputInfo& o : outputs) {
944                         o.primary_plane = o.crtc->get_primary_plane();
946                         if (!o.fbs.empty() && !o.primary_plane)
947                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
948                 }
949         }
951         if (!s_flip_mode)
952                 draw_test_patterns(outputs);
954         print_outputs(outputs);
956         if (card.has_atomic())
957                 set_crtcs_n_planes(card, outputs);
958         else
959                 set_crtcs_n_planes_legacy(card, outputs);
961         printf("press enter to exit\n");
963         if (s_flip_mode)
964                 main_flip(card, outputs);
965         else
966                 getchar();