cc895e3dde9496bec8c9ff47fa01e45b4421c582
[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:1920x1200@60
105         const regex mode_re("(?:(@?)(\\d+):)?"          // @12:
106                             "(?:(\\d+)x(\\d+)(i)?)"     // 1920x1200i
107                             "(?:@([\\d\\.]+))?");       // @60
109         smatch sm;
110         if (!regex_match(crtc_str, sm, mode_re))
111                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
113         if (sm[2].matched) {
114                 bool use_id = sm[1].length() == 1;
115                 unsigned num = stoul(sm[2].str());
117                 if (use_id) {
118                         Crtc* c = card.get_crtc(num);
119                         if (!c)
120                                 EXIT("Bad crtc id '%u'", num);
122                         output.crtc = c;
123                 } else {
124                         auto crtcs = card.get_crtcs();
126                         if (num >= crtcs.size())
127                                 EXIT("Bad crtc number '%u'", num);
129                         output.crtc = crtcs[num];
130                 }
131         } else {
132                 output.crtc = output.connector->get_current_crtc();
133         }
135         unsigned w = stoul(sm[3]);
136         unsigned h = stoul(sm[4]);
137         bool ilace = sm[5].matched ? true : false;
138         float refresh = sm[6].matched ? stof(sm[6]) : 0;
140         bool found_mode = false;
142         try {
143                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
144                 found_mode = true;
145         } catch (exception& e) { }
147         if (!found_mode && s_use_dmt) {
148                 try {
149                         output.mode = find_dmt(w, h, refresh, ilace);
150                         found_mode = true;
151                         printf("Found mode from DMT\n");
152                 } catch (exception& e) { }
153         }
155         if (!found_mode && s_use_cea) {
156                 try {
157                         output.mode = find_cea(w, h, refresh, ilace);
158                         found_mode = true;
159                         printf("Found mode from CEA\n");
160                 } catch (exception& e) { }
161         }
163         if (!found_mode)
164                 throw invalid_argument("Mode not found");
167 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
169         // 3:400,400-400x400
170         const regex plane_re("(?:(@?)(\\d+):)?"         // 3:
171                              "(?:(\\d+),(\\d+)-)?"      // 400,400-
172                              "(\\d+)x(\\d+)");          // 400x400
174         smatch sm;
175         if (!regex_match(plane_str, sm, plane_re))
176                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
178         if (sm[2].matched) {
179                 bool use_id = sm[1].length() == 1;
180                 unsigned num = stoul(sm[2].str());
182                 if (use_id) {
183                         Plane* p = card.get_plane(num);
184                         if (!p)
185                                 EXIT("Bad plane id '%u'", num);
187                         pinfo.plane = p;
188                 } else {
189                         auto planes = card.get_planes();
191                         if (num >= planes.size())
192                                 EXIT("Bad plane number '%u'", num);
194                         pinfo.plane = planes[num];
195                 }
196         } else {
197                 for (Plane* p : output.crtc->get_possible_planes()) {
198                         if (s_used_planes.find(p) != s_used_planes.end())
199                                 continue;
201                         if (p->plane_type() != PlaneType::Overlay)
202                                 continue;
204                         pinfo.plane = p;
205                 }
207                 if (!pinfo.plane)
208                         EXIT("Failed to find available plane");
209         }
211         s_used_planes.insert(pinfo.plane);
213         pinfo.w = stoul(sm[5]);
214         pinfo.h = stoul(sm[6]);
216         if (sm[3].matched)
217                 pinfo.x = stoul(sm[3]);
218         else
219                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
221         if (sm[4].matched)
222                 pinfo.y = stoul(sm[4]);
223         else
224                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
227 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
229         vector<DumbFramebuffer*> v;
231         for (unsigned i = 0; i < s_num_buffers; ++i)
232                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
234         return v;
237 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
239         unsigned w = def_w;
240         unsigned h = def_h;
241         PixelFormat format = PixelFormat::XRGB8888;
243         if (!fb_str.empty()) {
244                 // XXX the regexp is not quite correct
245                 // 400x400-NV12
246                 const regex fb_re("(?:(\\d+)x(\\d+))?"          // 400x400
247                                   "(?:-)?"                      // -
248                                   "(\\w\\w\\w\\w)?");           // NV12
250                 smatch sm;
251                 if (!regex_match(fb_str, sm, fb_re))
252                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
254                 if (sm[1].matched)
255                         w = stoul(sm[1]);
256                 if (sm[2].matched)
257                         h = stoul(sm[2]);
258                 if (sm[3].matched)
259                         format = FourCCToPixelFormat(sm[3]);
260         }
262         vector<DumbFramebuffer*> v;
264         for (unsigned i = 0; i < s_num_buffers; ++i)
265                 v.push_back(new DumbFramebuffer(card, w, h, format));
267         return v;
270 static const char* usage_str =
271                 "Usage: kmstest [OPTION]...\n\n"
272                 "Show a test pattern on a display or plane\n\n"
273                 "Options:\n"
274                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
275                 "  -c, --connector=CONN      CONN is <connector>\n"
276                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
277                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
278                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
279                 "      --dmt                 Search for the given mode from DMT tables\n"
280                 "      --cea                 Search for the given mode from CEA tables\n"
281                 "      --flip                Do page flipping for each output\n"
282                 "      --sync                Synchronize page flipping\n"
283                 "\n"
284                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
285                 "<connector> can also be given by name.\n"
286                 "\n"
287                 "Options can be given multiple times to set up multiple displays or planes.\n"
288                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
289                 "an earlier option.\n"
290                 "If you omit parameters, kmstest tries to guess what you mean\n"
291                 "\n"
292                 "Examples:\n"
293                 "\n"
294                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
295                 "    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
296                 "XR24 framebuffer on first connected connector in the default mode:\n"
297                 "    kmstest -f XR24\n\n"
298                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
299                 "    kmstest -p 400x400 -f XR24\n\n"
300                 "Test pattern on the second connector with default mode:\n"
301                 "    kmstest -c 1\n"
302                 ;
304 static void usage()
306         puts(usage_str);
309 enum class ObjectType
311         Connector,
312         Crtc,
313         Plane,
314         Framebuffer,
315 };
317 struct Arg
319         ObjectType type;
320         string arg;
321 };
323 static string s_device_path = "/dev/dri/card0";
325 static vector<Arg> parse_cmdline(int argc, char **argv)
327         vector<Arg> args;
329         OptionSet optionset = {
330                 Option("|device=",
331                 [&](string s)
332                 {
333                         s_device_path = s;
334                 }),
335                 Option("c|connector=",
336                 [&](string s)
337                 {
338                         args.push_back(Arg { ObjectType::Connector, s });
339                 }),
340                 Option("r|crtc=", [&](string s)
341                 {
342                         args.push_back(Arg { ObjectType::Crtc, s });
343                 }),
344                 Option("p|plane=", [&](string s)
345                 {
346                         args.push_back(Arg { ObjectType::Plane, s });
347                 }),
348                 Option("f|fb=", [&](string s)
349                 {
350                         args.push_back(Arg { ObjectType::Framebuffer, s });
351                 }),
352                 Option("|dmt", []()
353                 {
354                         s_use_dmt = true;
355                 }),
356                 Option("|cea", []()
357                 {
358                         s_use_cea = true;
359                 }),
360                 Option("|flip", []()
361                 {
362                         s_flip_mode = true;
363                         s_num_buffers = 2;
364                 }),
365                 Option("|sync", []()
366                 {
367                         s_flip_sync = true;
368                 }),
369                 Option("h|help", [&]()
370                 {
371                         usage();
372                         exit(-1);
373                 }),
374         };
376         optionset.parse(argc, argv);
378         if (optionset.params().size() > 0) {
379                 usage();
380                 exit(-1);
381         }
383         return args;
386 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
388         vector<OutputInfo> outputs;
390         if (output_args.size() == 0) {
391                 // no output args, show a pattern on all screens
392                 for (auto& pipe : card.get_connected_pipelines()) {
393                         OutputInfo output = { };
394                         output.connector = pipe.connector;
395                         output.crtc = pipe.crtc;
396                         output.mode = output.connector->get_default_mode();
398                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
400                         outputs.push_back(output);
401                 }
403                 return outputs;
404         }
406         OutputInfo* current_output = 0;
407         PlaneInfo* current_plane = 0;
409         for (auto& arg : output_args) {
410                 switch (arg.type) {
411                 case ObjectType::Connector:
412                 {
413                         outputs.push_back(OutputInfo { });
414                         current_output = &outputs.back();
416                         parse_connector(card, arg.arg, *current_output);
417                         current_plane = 0;
419                         break;
420                 }
422                 case ObjectType::Crtc:
423                 {
424                         if (!current_output) {
425                                 outputs.push_back(OutputInfo { });
426                                 current_output = &outputs.back();
427                         }
429                         if (!current_output->connector)
430                                 get_default_connector(card, *current_output);
432                         parse_crtc(card, arg.arg, *current_output);
434                         current_output->user_set_crtc = true;
436                         current_plane = 0;
438                         break;
439                 }
441                 case ObjectType::Plane:
442                 {
443                         if (!current_output) {
444                                 outputs.push_back(OutputInfo { });
445                                 current_output = &outputs.back();
446                         }
448                         if (!current_output->connector)
449                                 get_default_connector(card, *current_output);
451                         if (!current_output->crtc)
452                                 get_default_crtc(card, *current_output);
454                         current_output->planes.push_back(PlaneInfo { });
455                         current_plane = &current_output->planes.back();
457                         parse_plane(card, arg.arg, *current_output, *current_plane);
459                         break;
460                 }
462                 case ObjectType::Framebuffer:
463                 {
464                         if (!current_output) {
465                                 outputs.push_back(OutputInfo { });
466                                 current_output = &outputs.back();
467                         }
469                         if (!current_output->connector)
470                                 get_default_connector(card, *current_output);
472                         if (!current_output->crtc)
473                                 get_default_crtc(card, *current_output);
475                         int def_w, def_h;
477                         if (current_plane) {
478                                 def_w = current_plane->w;
479                                 def_h = current_plane->h;
480                         } else {
481                                 def_w = current_output->mode.hdisplay;
482                                 def_h = current_output->mode.vdisplay;
483                         }
485                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
487                         if (current_plane)
488                                 current_plane->fbs = fbs;
489                         else
490                                 current_output->fbs = fbs;
492                         break;
493                 }
494                 }
495         }
497         // create default framebuffers if needed
498         for (OutputInfo& o : outputs) {
499                 if (!o.crtc) {
500                         get_default_crtc(card, *current_output);
501                         o.user_set_crtc = true;
502                 }
504                 if (o.fbs.empty() && o.user_set_crtc)
505                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
507                 for (PlaneInfo &p : o.planes) {
508                         if (p.fbs.empty())
509                                 p.fbs = get_default_fb(card, p.w, p.h);
510                 }
511         }
513         return outputs;
516 static std::string videomode_to_string(const Videomode& m)
518         string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
519         string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
521         return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
522                        m.name.c_str(),
523                        m.clock / 1000.0,
524                        h.c_str(), v.c_str(),
525                        m.vrefresh, m.calculated_vrefresh(),
526                        m.flags,
527                        m.type);
530 static void print_outputs(const vector<OutputInfo>& outputs)
532         for (unsigned i = 0; i < outputs.size(); ++i) {
533                 const OutputInfo& o = outputs[i];
535                 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
536                        o.connector->fullname().c_str());
537                 printf("  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
538                 if (o.primary_plane)
539                         printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
540                 printf(": %s\n", videomode_to_string(o.mode).c_str());
541                 if (!o.fbs.empty()) {
542                         auto fb = o.fbs[0];
543                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
544                                PixelFormatToFourCC(fb->format()).c_str());
545                 }
547                 for (unsigned j = 0; j < o.planes.size(); ++j) {
548                         const PlaneInfo& p = o.planes[j];
549                         auto fb = p.fbs[0];
550                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
551                                p.x, p.y, p.w, p.h);
552                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
553                                PixelFormatToFourCC(fb->format()).c_str());
554                 }
555         }
558 static void draw_test_patterns(const vector<OutputInfo>& outputs)
560         for (const OutputInfo& o : outputs) {
561                 for (auto fb : o.fbs)
562                         draw_test_pattern(*fb);
564                 for (const PlaneInfo& p : o.planes)
565                         for (auto fb : p.fbs)
566                                 draw_test_pattern(*fb);
567         }
570 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
572         for (const OutputInfo& o : outputs) {
573                 auto conn = o.connector;
574                 auto crtc = o.crtc;
576                 if (!o.fbs.empty()) {
577                         auto fb = o.fbs[0];
578                         int r = crtc->set_mode(conn, *fb, o.mode);
579                         if (r)
580                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
581                                        crtc->id(), strerror(-r));
582                 }
584                 for (const PlaneInfo& p : o.planes) {
585                         auto fb = p.fbs[0];
586                         int r = crtc->set_plane(p.plane, *fb,
587                                                 p.x, p.y, p.w, p.h,
588                                                 0, 0, fb->width(), fb->height());
589                         if (r)
590                                 printf("crtc->set_plane() failed for plane %u: %s\n",
591                                        p.plane->id(), strerror(-r));
592                 }
593         }
596 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
598         // Keep blobs here so that we keep ref to them until we have committed the req
599         vector<unique_ptr<Blob>> blobs;
601         AtomicReq req(card);
603         for (const OutputInfo& o : outputs) {
604                 auto conn = o.connector;
605                 auto crtc = o.crtc;
607                 if (!o.fbs.empty()) {
608                         auto fb = o.fbs[0];
610                         blobs.emplace_back(o.mode.to_blob(card));
611                         Blob* mode_blob = blobs.back().get();
613                         req.add(conn, {
614                                         { "CRTC_ID", crtc->id() },
615                                 });
617                         req.add(crtc, {
618                                         { "ACTIVE", 1 },
619                                         { "MODE_ID", mode_blob->id() },
620                                 });
622                         req.add(o.primary_plane, {
623                                         { "FB_ID", fb->id() },
624                                         { "CRTC_ID", crtc->id() },
625                                         { "SRC_X", 0 << 16 },
626                                         { "SRC_Y", 0 << 16 },
627                                         { "SRC_W", fb->width() << 16 },
628                                         { "SRC_H", fb->height() << 16 },
629                                         { "CRTC_X", 0 },
630                                         { "CRTC_Y", 0 },
631                                         { "CRTC_W", fb->width() },
632                                         { "CRTC_H", fb->height() },
633                                 });
634                 }
636                 for (const PlaneInfo& p : o.planes) {
637                         auto fb = p.fbs[0];
639                         req.add(p.plane, {
640                                         { "FB_ID", fb->id() },
641                                         { "CRTC_ID", crtc->id() },
642                                         { "SRC_X", 0 << 16 },
643                                         { "SRC_Y", 0 << 16 },
644                                         { "SRC_W", fb->width() << 16 },
645                                         { "SRC_H", fb->height() << 16 },
646                                         { "CRTC_X", p.x },
647                                         { "CRTC_Y", p.y },
648                                         { "CRTC_W", p.w },
649                                         { "CRTC_H", p.h },
650                                 });
651                 }
652         }
654         int r;
656         r = req.test(true);
657         if (r)
658                 EXIT("Atomic test failed: %d\n", r);
660         r = req.commit_sync(true);
661         if (r)
662                 EXIT("Atomic commit failed: %d\n", r);
665 class FlipState : private PageFlipHandlerBase
667 public:
668         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
669                 : m_card(card), m_name(name), m_outputs(outputs)
670         {
671         }
673         void start_flipping()
674         {
675                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
676                 m_slowest_frame = std::chrono::duration<float>::min();
677                 m_frame_num = 0;
678                 queue_next();
679         }
681 private:
682         void handle_page_flip(uint32_t frame, double time)
683         {
684                 m_frame_num++;
686                 auto now = std::chrono::steady_clock::now();
688                 std::chrono::duration<float> diff = now - m_prev_frame;
689                 if (diff > m_slowest_frame)
690                         m_slowest_frame = diff;
692                 if (m_frame_num  % 100 == 0) {
693                         std::chrono::duration<float> fsec = now - m_prev_print;
694                         printf("Connector %s: fps %f, slowest %.2f ms\n",
695                                m_name.c_str(),
696                                100.0 / fsec.count(),
697                                m_slowest_frame.count() * 1000);
698                         m_prev_print = now;
699                         m_slowest_frame = std::chrono::duration<float>::min();
700                 }
702                 m_prev_frame = now;
704                 queue_next();
705         }
707         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
708         {
709                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
710         }
712         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
713         {
714                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
715                 int new_xpos = get_bar_pos(fb, frame_num);
717                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
718                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
719         }
721         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
722         {
723                 unsigned cur = frame_num % s_num_buffers;
725                 if (!o.fbs.empty()) {
726                         auto fb = o.fbs[cur];
728                         draw_bar(fb, frame_num);
730                         req.add(o.primary_plane, {
731                                         { "FB_ID", fb->id() },
732                                 });
733                 }
735                 for (const PlaneInfo& p : o.planes) {
736                         auto fb = p.fbs[cur];
738                         draw_bar(fb, frame_num);
740                         req.add(p.plane, {
741                                         { "FB_ID", fb->id() },
742                                 });
743                 }
744         }
746         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
747         {
748                 unsigned cur = frame_num % s_num_buffers;
750                 if (!o.fbs.empty()) {
751                         auto fb = o.fbs[cur];
753                         draw_bar(fb, frame_num);
755                         int r = o.crtc->page_flip(*fb, this);
756                         ASSERT(r == 0);
757                 }
759                 for (const PlaneInfo& p : o.planes) {
760                         auto fb = p.fbs[cur];
762                         draw_bar(fb, frame_num);
764                         int r = o.crtc->set_plane(p.plane, *fb,
765                                                   p.x, p.y, p.w, p.h,
766                                                   0, 0, fb->width(), fb->height());
767                         ASSERT(r == 0);
768                 }
769         }
771         void queue_next()
772         {
773                 if (m_card.has_atomic()) {
774                         AtomicReq req(m_card);
776                         for (auto o : m_outputs)
777                                 do_flip_output(req, m_frame_num, *o);
779                         int r = req.commit(this);
780                         if (r)
781                                 EXIT("Flip commit failed: %d\n", r);
782                 } else {
783                         ASSERT(m_outputs.size() == 1);
784                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
785                 }
786         }
788         Card& m_card;
789         string m_name;
790         vector<const OutputInfo*> m_outputs;
791         unsigned m_frame_num;
793         chrono::steady_clock::time_point m_prev_print;
794         chrono::steady_clock::time_point m_prev_frame;
795         chrono::duration<float> m_slowest_frame;
797         static const unsigned bar_width = 20;
798         static const unsigned bar_speed = 8;
799 };
801 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
803         fd_set fds;
805         FD_ZERO(&fds);
807         int fd = card.fd();
809         vector<unique_ptr<FlipState>> flipstates;
811         if (!s_flip_sync) {
812                 for (const OutputInfo& o : outputs) {
813                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
814                         flipstates.push_back(move(fs));
815                 }
816         } else {
817                 vector<const OutputInfo*> ois;
819                 string name;
820                 for (const OutputInfo& o : outputs) {
821                         name += to_string(o.connector->idx()) + ",";
822                         ois.push_back(&o);
823                 }
825                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
826                 flipstates.push_back(move(fs));
827         }
829         for (unique_ptr<FlipState>& fs : flipstates)
830                 fs->start_flipping();
832         while (true) {
833                 int r;
835                 FD_SET(0, &fds);
836                 FD_SET(fd, &fds);
838                 r = select(fd + 1, &fds, NULL, NULL, NULL);
839                 if (r < 0) {
840                         fprintf(stderr, "select() failed with %d: %m\n", errno);
841                         break;
842                 } else if (FD_ISSET(0, &fds)) {
843                         fprintf(stderr, "Exit due to user-input\n");
844                         break;
845                 } else if (FD_ISSET(fd, &fds)) {
846                         card.call_page_flip_handlers();
847                 }
848         }
851 int main(int argc, char **argv)
853         vector<Arg> output_args = parse_cmdline(argc, argv);
855         Card card(s_device_path);
857         if (!card.has_atomic() && s_flip_sync)
858                 EXIT("Synchronized flipping requires atomic modesetting");
860         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
862         if (card.has_atomic()) {
863                 for (OutputInfo& o : outputs) {
864                         o.primary_plane = o.crtc->get_primary_plane();
866                         if (!o.fbs.empty() && !o.primary_plane)
867                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
868                 }
869         }
871         if (!s_flip_mode)
872                 draw_test_patterns(outputs);
874         print_outputs(outputs);
876         if (card.has_atomic())
877                 set_crtcs_n_planes(card, outputs);
878         else
879                 set_crtcs_n_planes_legacy(card, outputs);
881         printf("press enter to exit\n");
883         if (s_flip_mode)
884                 main_flip(card, outputs);
885         else
886                 getchar();