kmstest: add --cvt option
[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                 bool found_mode = false;
153                 if (s_cvt) {
154                         output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
155                         found_mode = true;
156                 }
158                 if (!found_mode) {
159                         try {
160                                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
161                                 found_mode = true;
162                         } catch (exception& e) { }
163                 }
165                 if (!found_mode && s_use_dmt) {
166                         try {
167                                 output.mode = find_dmt(w, h, refresh, ilace);
168                                 found_mode = true;
169                                 printf("Found mode from DMT\n");
170                         } catch (exception& e) { }
171                 }
173                 if (!found_mode && s_use_cea) {
174                         try {
175                                 output.mode = find_cea(w, h, refresh, ilace);
176                                 found_mode = true;
177                                 printf("Found mode from CEA\n");
178                         } catch (exception& e) { }
179                 }
181                 if (!found_mode)
182                         throw invalid_argument("Mode not found");
183         } else if (regex_match(crtc_str, sm, modeline_re)) {
184                 if (sm[2].matched) {
185                         bool use_id = sm[1].length() == 1;
186                         unsigned num = stoul(sm[2].str());
188                         if (use_id) {
189                                 Crtc* c = card.get_crtc(num);
190                                 if (!c)
191                                         EXIT("Bad crtc id '%u'", num);
193                                 output.crtc = c;
194                         } else {
195                                 auto crtcs = card.get_crtcs();
197                                 if (num >= crtcs.size())
198                                         EXIT("Bad crtc number '%u'", num);
200                                 output.crtc = crtcs[num];
201                         }
202                 } else {
203                         output.crtc = output.connector->get_current_crtc();
204                 }
206                 unsigned clock = stoul(sm[3]);
208                 unsigned hact = stoul(sm[4]);
209                 unsigned hfp = stoul(sm[5]);
210                 unsigned hsw = stoul(sm[6]);
211                 unsigned hbp = stoul(sm[7]);
212                 bool h_pos_sync = sm[8] == "+" ? true : false;
214                 unsigned vact = stoul(sm[9]);
215                 unsigned vfp = stoul(sm[10]);
216                 unsigned vsw = stoul(sm[11]);
217                 unsigned vbp = stoul(sm[12]);
218                 bool v_pos_sync = sm[13] == "+" ? true : false;
220                 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
221                 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
222                 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
224                 if (sm[14].matched) {
225                         for (int i = 0; i < sm[14].length(); ++i) {
226                                 char f = string(sm[14])[i];
228                                 switch (f) {
229                                 case 'i':
230                                         output.mode.set_interlace(true);
231                                         break;
232                                 default:
233                                         EXIT("Bad mode flag %c", f);
234                                 }
235                         }
236                 }
237         } else {
238                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
239         }
242 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
244         // 3:400,400-400x400
245         const regex plane_re("(?:(@?)(\\d+):)?"         // 3:
246                              "(?:(\\d+),(\\d+)-)?"      // 400,400-
247                              "(\\d+)x(\\d+)");          // 400x400
249         smatch sm;
250         if (!regex_match(plane_str, sm, plane_re))
251                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
253         if (sm[2].matched) {
254                 bool use_id = sm[1].length() == 1;
255                 unsigned num = stoul(sm[2].str());
257                 if (use_id) {
258                         Plane* p = card.get_plane(num);
259                         if (!p)
260                                 EXIT("Bad plane id '%u'", num);
262                         pinfo.plane = p;
263                 } else {
264                         auto planes = card.get_planes();
266                         if (num >= planes.size())
267                                 EXIT("Bad plane number '%u'", num);
269                         pinfo.plane = planes[num];
270                 }
271         } else {
272                 for (Plane* p : output.crtc->get_possible_planes()) {
273                         if (s_used_planes.find(p) != s_used_planes.end())
274                                 continue;
276                         if (p->plane_type() != PlaneType::Overlay)
277                                 continue;
279                         pinfo.plane = p;
280                 }
282                 if (!pinfo.plane)
283                         EXIT("Failed to find available plane");
284         }
286         s_used_planes.insert(pinfo.plane);
288         pinfo.w = stoul(sm[5]);
289         pinfo.h = stoul(sm[6]);
291         if (sm[3].matched)
292                 pinfo.x = stoul(sm[3]);
293         else
294                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
296         if (sm[4].matched)
297                 pinfo.y = stoul(sm[4]);
298         else
299                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
302 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
304         vector<DumbFramebuffer*> v;
306         for (unsigned i = 0; i < s_num_buffers; ++i)
307                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
309         return v;
312 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
314         unsigned w = def_w;
315         unsigned h = def_h;
316         PixelFormat format = PixelFormat::XRGB8888;
318         if (!fb_str.empty()) {
319                 // XXX the regexp is not quite correct
320                 // 400x400-NV12
321                 const regex fb_re("(?:(\\d+)x(\\d+))?"          // 400x400
322                                   "(?:-)?"                      // -
323                                   "(\\w\\w\\w\\w)?");           // NV12
325                 smatch sm;
326                 if (!regex_match(fb_str, sm, fb_re))
327                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
329                 if (sm[1].matched)
330                         w = stoul(sm[1]);
331                 if (sm[2].matched)
332                         h = stoul(sm[2]);
333                 if (sm[3].matched)
334                         format = FourCCToPixelFormat(sm[3]);
335         }
337         vector<DumbFramebuffer*> v;
339         for (unsigned i = 0; i < s_num_buffers; ++i)
340                 v.push_back(new DumbFramebuffer(card, w, h, format));
342         return v;
345 static const char* usage_str =
346                 "Usage: kmstest [OPTION]...\n\n"
347                 "Show a test pattern on a display or plane\n\n"
348                 "Options:\n"
349                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
350                 "  -c, --connector=CONN      CONN is <connector>\n"
351                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
352                 "                            or\n"
353                 "                            [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
354                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
355                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
356                 "      --dmt                 Search for the given mode from DMT tables\n"
357                 "      --cea                 Search for the given mode from CEA tables\n"
358                 "      --cvt=CVT             Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
359                 "      --flip                Do page flipping for each output\n"
360                 "      --sync                Synchronize page flipping\n"
361                 "\n"
362                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
363                 "<connector> can also be given by name.\n"
364                 "\n"
365                 "Options can be given multiple times to set up multiple displays or planes.\n"
366                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
367                 "an earlier option.\n"
368                 "If you omit parameters, kmstest tries to guess what you mean\n"
369                 "\n"
370                 "Examples:\n"
371                 "\n"
372                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
373                 "    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
374                 "XR24 framebuffer on first connected connector in the default mode:\n"
375                 "    kmstest -f XR24\n\n"
376                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
377                 "    kmstest -p 400x400 -f XR24\n\n"
378                 "Test pattern on the second connector with default mode:\n"
379                 "    kmstest -c 1\n"
380                 ;
382 static void usage()
384         puts(usage_str);
387 enum class ObjectType
389         Connector,
390         Crtc,
391         Plane,
392         Framebuffer,
393 };
395 struct Arg
397         ObjectType type;
398         string arg;
399 };
401 static string s_device_path = "/dev/dri/card0";
403 static vector<Arg> parse_cmdline(int argc, char **argv)
405         vector<Arg> args;
407         OptionSet optionset = {
408                 Option("|device=",
409                 [&](string s)
410                 {
411                         s_device_path = s;
412                 }),
413                 Option("c|connector=",
414                 [&](string s)
415                 {
416                         args.push_back(Arg { ObjectType::Connector, s });
417                 }),
418                 Option("r|crtc=", [&](string s)
419                 {
420                         args.push_back(Arg { ObjectType::Crtc, s });
421                 }),
422                 Option("p|plane=", [&](string s)
423                 {
424                         args.push_back(Arg { ObjectType::Plane, s });
425                 }),
426                 Option("f|fb=", [&](string s)
427                 {
428                         args.push_back(Arg { ObjectType::Framebuffer, s });
429                 }),
430                 Option("|dmt", []()
431                 {
432                         s_use_dmt = true;
433                 }),
434                 Option("|cea", []()
435                 {
436                         s_use_cea = true;
437                 }),
438                 Option("|flip", []()
439                 {
440                         s_flip_mode = true;
441                         s_num_buffers = 2;
442                 }),
443                 Option("|sync", []()
444                 {
445                         s_flip_sync = true;
446                 }),
447                 Option("|cvt=", [&](string s)
448                 {
449                         if (s == "v1")
450                                 s_cvt = true;
451                         else if (s == "v2")
452                                 s_cvt = s_cvt_v2 = true;
453                         else if (s == "v2o")
454                                 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
455                         else {
456                                 usage();
457                                 exit(-1);
458                         }
459                 }),
460                 Option("h|help", [&]()
461                 {
462                         usage();
463                         exit(-1);
464                 }),
465         };
467         optionset.parse(argc, argv);
469         if (optionset.params().size() > 0) {
470                 usage();
471                 exit(-1);
472         }
474         return args;
477 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
479         vector<OutputInfo> outputs;
481         if (output_args.size() == 0) {
482                 // no output args, show a pattern on all screens
483                 for (auto& pipe : card.get_connected_pipelines()) {
484                         OutputInfo output = { };
485                         output.connector = pipe.connector;
486                         output.crtc = pipe.crtc;
487                         output.mode = output.connector->get_default_mode();
489                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
491                         outputs.push_back(output);
492                 }
494                 return outputs;
495         }
497         OutputInfo* current_output = 0;
498         PlaneInfo* current_plane = 0;
500         for (auto& arg : output_args) {
501                 switch (arg.type) {
502                 case ObjectType::Connector:
503                 {
504                         outputs.push_back(OutputInfo { });
505                         current_output = &outputs.back();
507                         parse_connector(card, arg.arg, *current_output);
508                         current_plane = 0;
510                         break;
511                 }
513                 case ObjectType::Crtc:
514                 {
515                         if (!current_output) {
516                                 outputs.push_back(OutputInfo { });
517                                 current_output = &outputs.back();
518                         }
520                         if (!current_output->connector)
521                                 get_default_connector(card, *current_output);
523                         parse_crtc(card, arg.arg, *current_output);
525                         current_output->user_set_crtc = true;
527                         current_plane = 0;
529                         break;
530                 }
532                 case ObjectType::Plane:
533                 {
534                         if (!current_output) {
535                                 outputs.push_back(OutputInfo { });
536                                 current_output = &outputs.back();
537                         }
539                         if (!current_output->connector)
540                                 get_default_connector(card, *current_output);
542                         if (!current_output->crtc)
543                                 get_default_crtc(card, *current_output);
545                         current_output->planes.push_back(PlaneInfo { });
546                         current_plane = &current_output->planes.back();
548                         parse_plane(card, arg.arg, *current_output, *current_plane);
550                         break;
551                 }
553                 case ObjectType::Framebuffer:
554                 {
555                         if (!current_output) {
556                                 outputs.push_back(OutputInfo { });
557                                 current_output = &outputs.back();
558                         }
560                         if (!current_output->connector)
561                                 get_default_connector(card, *current_output);
563                         if (!current_output->crtc)
564                                 get_default_crtc(card, *current_output);
566                         int def_w, def_h;
568                         if (current_plane) {
569                                 def_w = current_plane->w;
570                                 def_h = current_plane->h;
571                         } else {
572                                 def_w = current_output->mode.hdisplay;
573                                 def_h = current_output->mode.vdisplay;
574                         }
576                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
578                         if (current_plane)
579                                 current_plane->fbs = fbs;
580                         else
581                                 current_output->fbs = fbs;
583                         break;
584                 }
585                 }
586         }
588         // create default framebuffers if needed
589         for (OutputInfo& o : outputs) {
590                 if (!o.crtc) {
591                         get_default_crtc(card, *current_output);
592                         o.user_set_crtc = true;
593                 }
595                 if (o.fbs.empty() && o.user_set_crtc)
596                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
598                 for (PlaneInfo &p : o.planes) {
599                         if (p.fbs.empty())
600                                 p.fbs = get_default_fb(card, p.w, p.h);
601                 }
602         }
604         return outputs;
607 static std::string videomode_to_string(const Videomode& m)
609         string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
610         string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
612         return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
613                        m.name.c_str(),
614                        m.clock / 1000.0,
615                        h.c_str(), v.c_str(),
616                        m.vrefresh, m.calculated_vrefresh(),
617                        m.flags,
618                        m.type);
621 static void print_outputs(const vector<OutputInfo>& outputs)
623         for (unsigned i = 0; i < outputs.size(); ++i) {
624                 const OutputInfo& o = outputs[i];
626                 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
627                        o.connector->fullname().c_str());
628                 printf("  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
629                 if (o.primary_plane)
630                         printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
631                 printf(": %s\n", videomode_to_string(o.mode).c_str());
632                 if (!o.fbs.empty()) {
633                         auto fb = o.fbs[0];
634                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
635                                PixelFormatToFourCC(fb->format()).c_str());
636                 }
638                 for (unsigned j = 0; j < o.planes.size(); ++j) {
639                         const PlaneInfo& p = o.planes[j];
640                         auto fb = p.fbs[0];
641                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
642                                p.x, p.y, p.w, p.h);
643                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
644                                PixelFormatToFourCC(fb->format()).c_str());
645                 }
646         }
649 static void draw_test_patterns(const vector<OutputInfo>& outputs)
651         for (const OutputInfo& o : outputs) {
652                 for (auto fb : o.fbs)
653                         draw_test_pattern(*fb);
655                 for (const PlaneInfo& p : o.planes)
656                         for (auto fb : p.fbs)
657                                 draw_test_pattern(*fb);
658         }
661 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
663         for (const OutputInfo& o : outputs) {
664                 auto conn = o.connector;
665                 auto crtc = o.crtc;
667                 if (!o.fbs.empty()) {
668                         auto fb = o.fbs[0];
669                         int r = crtc->set_mode(conn, *fb, o.mode);
670                         if (r)
671                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
672                                        crtc->id(), strerror(-r));
673                 }
675                 for (const PlaneInfo& p : o.planes) {
676                         auto fb = p.fbs[0];
677                         int r = crtc->set_plane(p.plane, *fb,
678                                                 p.x, p.y, p.w, p.h,
679                                                 0, 0, fb->width(), fb->height());
680                         if (r)
681                                 printf("crtc->set_plane() failed for plane %u: %s\n",
682                                        p.plane->id(), strerror(-r));
683                 }
684         }
687 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
689         // Keep blobs here so that we keep ref to them until we have committed the req
690         vector<unique_ptr<Blob>> blobs;
692         AtomicReq req(card);
694         for (const OutputInfo& o : outputs) {
695                 auto conn = o.connector;
696                 auto crtc = o.crtc;
698                 blobs.emplace_back(o.mode.to_blob(card));
699                 Blob* mode_blob = blobs.back().get();
701                 req.add(conn, {
702                                 { "CRTC_ID", crtc->id() },
703                         });
705                 req.add(crtc, {
706                                 { "ACTIVE", 1 },
707                                 { "MODE_ID", mode_blob->id() },
708                         });
710                 if (!o.fbs.empty()) {
711                         auto fb = o.fbs[0];
713                         req.add(o.primary_plane, {
714                                         { "FB_ID", fb->id() },
715                                         { "CRTC_ID", crtc->id() },
716                                         { "SRC_X", 0 << 16 },
717                                         { "SRC_Y", 0 << 16 },
718                                         { "SRC_W", fb->width() << 16 },
719                                         { "SRC_H", fb->height() << 16 },
720                                         { "CRTC_X", 0 },
721                                         { "CRTC_Y", 0 },
722                                         { "CRTC_W", fb->width() },
723                                         { "CRTC_H", fb->height() },
724                                 });
725                 }
727                 for (const PlaneInfo& p : o.planes) {
728                         auto fb = p.fbs[0];
730                         req.add(p.plane, {
731                                         { "FB_ID", fb->id() },
732                                         { "CRTC_ID", crtc->id() },
733                                         { "SRC_X", 0 << 16 },
734                                         { "SRC_Y", 0 << 16 },
735                                         { "SRC_W", fb->width() << 16 },
736                                         { "SRC_H", fb->height() << 16 },
737                                         { "CRTC_X", p.x },
738                                         { "CRTC_Y", p.y },
739                                         { "CRTC_W", p.w },
740                                         { "CRTC_H", p.h },
741                                 });
742                 }
743         }
745         int r;
747         r = req.test(true);
748         if (r)
749                 EXIT("Atomic test failed: %d\n", r);
751         r = req.commit_sync(true);
752         if (r)
753                 EXIT("Atomic commit failed: %d\n", r);
756 class FlipState : private PageFlipHandlerBase
758 public:
759         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
760                 : m_card(card), m_name(name), m_outputs(outputs)
761         {
762         }
764         void start_flipping()
765         {
766                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
767                 m_slowest_frame = std::chrono::duration<float>::min();
768                 m_frame_num = 0;
769                 queue_next();
770         }
772 private:
773         void handle_page_flip(uint32_t frame, double time)
774         {
775                 m_frame_num++;
777                 auto now = std::chrono::steady_clock::now();
779                 std::chrono::duration<float> diff = now - m_prev_frame;
780                 if (diff > m_slowest_frame)
781                         m_slowest_frame = diff;
783                 if (m_frame_num  % 100 == 0) {
784                         std::chrono::duration<float> fsec = now - m_prev_print;
785                         printf("Connector %s: fps %f, slowest %.2f ms\n",
786                                m_name.c_str(),
787                                100.0 / fsec.count(),
788                                m_slowest_frame.count() * 1000);
789                         m_prev_print = now;
790                         m_slowest_frame = std::chrono::duration<float>::min();
791                 }
793                 m_prev_frame = now;
795                 queue_next();
796         }
798         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
799         {
800                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
801         }
803         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
804         {
805                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
806                 int new_xpos = get_bar_pos(fb, frame_num);
808                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
809                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
810         }
812         static void do_flip_output(AtomicReq& req, 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                         req.add(o.primary_plane, {
822                                         { "FB_ID", fb->id() },
823                                 });
824                 }
826                 for (const PlaneInfo& p : o.planes) {
827                         auto fb = p.fbs[cur];
829                         draw_bar(fb, frame_num);
831                         req.add(p.plane, {
832                                         { "FB_ID", fb->id() },
833                                 });
834                 }
835         }
837         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
838         {
839                 unsigned cur = frame_num % s_num_buffers;
841                 if (!o.fbs.empty()) {
842                         auto fb = o.fbs[cur];
844                         draw_bar(fb, frame_num);
846                         int r = o.crtc->page_flip(*fb, this);
847                         ASSERT(r == 0);
848                 }
850                 for (const PlaneInfo& p : o.planes) {
851                         auto fb = p.fbs[cur];
853                         draw_bar(fb, frame_num);
855                         int r = o.crtc->set_plane(p.plane, *fb,
856                                                   p.x, p.y, p.w, p.h,
857                                                   0, 0, fb->width(), fb->height());
858                         ASSERT(r == 0);
859                 }
860         }
862         void queue_next()
863         {
864                 if (m_card.has_atomic()) {
865                         AtomicReq req(m_card);
867                         for (auto o : m_outputs)
868                                 do_flip_output(req, m_frame_num, *o);
870                         int r = req.commit(this);
871                         if (r)
872                                 EXIT("Flip commit failed: %d\n", r);
873                 } else {
874                         ASSERT(m_outputs.size() == 1);
875                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
876                 }
877         }
879         Card& m_card;
880         string m_name;
881         vector<const OutputInfo*> m_outputs;
882         unsigned m_frame_num;
884         chrono::steady_clock::time_point m_prev_print;
885         chrono::steady_clock::time_point m_prev_frame;
886         chrono::duration<float> m_slowest_frame;
888         static const unsigned bar_width = 20;
889         static const unsigned bar_speed = 8;
890 };
892 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
894         fd_set fds;
896         FD_ZERO(&fds);
898         int fd = card.fd();
900         vector<unique_ptr<FlipState>> flipstates;
902         if (!s_flip_sync) {
903                 for (const OutputInfo& o : outputs) {
904                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
905                         flipstates.push_back(move(fs));
906                 }
907         } else {
908                 vector<const OutputInfo*> ois;
910                 string name;
911                 for (const OutputInfo& o : outputs) {
912                         name += to_string(o.connector->idx()) + ",";
913                         ois.push_back(&o);
914                 }
916                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
917                 flipstates.push_back(move(fs));
918         }
920         for (unique_ptr<FlipState>& fs : flipstates)
921                 fs->start_flipping();
923         while (true) {
924                 int r;
926                 FD_SET(0, &fds);
927                 FD_SET(fd, &fds);
929                 r = select(fd + 1, &fds, NULL, NULL, NULL);
930                 if (r < 0) {
931                         fprintf(stderr, "select() failed with %d: %m\n", errno);
932                         break;
933                 } else if (FD_ISSET(0, &fds)) {
934                         fprintf(stderr, "Exit due to user-input\n");
935                         break;
936                 } else if (FD_ISSET(fd, &fds)) {
937                         card.call_page_flip_handlers();
938                 }
939         }
942 int main(int argc, char **argv)
944         vector<Arg> output_args = parse_cmdline(argc, argv);
946         Card card(s_device_path);
948         if (!card.has_atomic() && s_flip_sync)
949                 EXIT("Synchronized flipping requires atomic modesetting");
951         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
953         if (card.has_atomic()) {
954                 for (OutputInfo& o : outputs) {
955                         o.primary_plane = o.crtc->get_primary_plane();
957                         if (!o.fbs.empty() && !o.primary_plane)
958                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
959                 }
960         }
962         if (!s_flip_mode)
963                 draw_test_patterns(outputs);
965         print_outputs(outputs);
967         if (card.has_atomic())
968                 set_crtcs_n_planes(card, outputs);
969         else
970                 set_crtcs_n_planes_legacy(card, outputs);
972         printf("press enter to exit\n");
974         if (s_flip_mode)
975                 main_flip(card, outputs);
976         else
977                 getchar();