cmake: results to bin & lib dirs
[android/external-libkmsxx.git] / utils / testpat.cpp
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
8 #include <kms++.h>
9 #include <modedb.h>
11 #include <kms++util.h>
12 #include <opts.h>
14 using namespace std;
15 using namespace kms;
17 struct PlaneInfo
18 {
19         Plane* plane;
21         unsigned x;
22         unsigned y;
23         unsigned w;
24         unsigned h;
26         vector<DumbFramebuffer*> fbs;
27 };
29 struct OutputInfo
30 {
31         Connector* connector;
33         Crtc* crtc;
34         Plane* primary_plane;
35         Videomode mode;
36         bool user_set_crtc;
37         vector<DumbFramebuffer*> fbs;
39         vector<PlaneInfo> planes;
40 };
42 static bool s_use_dmt;
43 static bool s_use_cea;
44 static unsigned s_num_buffers = 1;
45 static bool s_flip_mode;
46 static bool s_flip_sync;
48 static set<Crtc*> s_used_crtcs;
49 static set<Plane*> s_used_planes;
51 __attribute__ ((unused))
52 static void print_regex_match(smatch sm)
53 {
54         for (unsigned i = 0; i < sm.size(); ++i) {
55                 string str = sm[i].str();
56                 printf("%u: %s\n", i, str.c_str());
57         }
58 }
60 static void get_default_connector(Card& card, OutputInfo& output)
61 {
62         output.connector = card.get_first_connected_connector();
63         output.mode = output.connector->get_default_mode();
64 }
66 static void parse_connector(Card& card, const string& str, OutputInfo& output)
67 {
68         Connector* conn = resolve_connector(card, str);
70         if (!conn)
71                 EXIT("No connector '%s'", str.c_str());
73         if (!conn->connected())
74                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
76         output.connector = conn;
77         output.mode = output.connector->get_default_mode();
78 }
80 static void get_default_crtc(Card& card, OutputInfo& output)
81 {
82         Crtc* crtc = output.connector->get_current_crtc();
84         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
85                 s_used_crtcs.insert(crtc);
86                 output.crtc = crtc;
87                 return;
88         }
90         for (const auto& possible : output.connector->get_possible_crtcs()) {
91                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
92                         s_used_crtcs.insert(possible);
93                         output.crtc = possible;
94                         return;
95                 }
96         }
98         EXIT("Could not find available crtc");
99 }
101 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
103         // @12:1920x1200@60
104         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
106         smatch sm;
107         if (!regex_match(crtc_str, sm, mode_re))
108                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
110         if (sm[2].matched) {
111                 bool use_id = sm[1].length() == 1;
112                 unsigned num = stoul(sm[2].str());
114                 if (use_id) {
115                         Crtc* c = card.get_crtc(num);
116                         if (!c)
117                                 EXIT("Bad crtc id '%u'", num);
119                         output.crtc = c;
120                 } else {
121                         auto crtcs = card.get_crtcs();
123                         if (num >= crtcs.size())
124                                 EXIT("Bad crtc number '%u'", num);
126                         output.crtc = crtcs[num];
127                 }
128         } else {
129                 output.crtc = output.connector->get_current_crtc();
130         }
132         unsigned w = stoul(sm[3]);
133         unsigned h = stoul(sm[4]);
134         bool ilace = sm[5].matched ? true : false;
135         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
137         bool found_mode = false;
139         try {
140                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
141                 found_mode = true;
142         } catch (exception& e) { }
144         if (!found_mode && s_use_dmt) {
145                 try {
146                         output.mode = find_dmt(w, h, refresh, ilace);
147                         found_mode = true;
148                         printf("Found mode from DMT\n");
149                 } catch (exception& e) { }
150         }
152         if (!found_mode && s_use_cea) {
153                 try {
154                         output.mode = find_cea(w, h, refresh, ilace);
155                         found_mode = true;
156                         printf("Found mode from CEA\n");
157                 } catch (exception& e) { }
158         }
160         if (!found_mode)
161                 throw invalid_argument("Mode not found");
164 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
166         // 3:400,400-400x400
167         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
169         smatch sm;
170         if (!regex_match(plane_str, sm, plane_re))
171                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
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                         Plane* p = card.get_plane(num);
179                         if (!p)
180                                 EXIT("Bad plane id '%u'", num);
182                         pinfo.plane = p;
183                 } else {
184                         auto planes = card.get_planes();
186                         if (num >= planes.size())
187                                 EXIT("Bad plane number '%u'", num);
189                         pinfo.plane = planes[num];
190                 }
191         } else {
192                 for (Plane* p : output.crtc->get_possible_planes()) {
193                         if (s_used_planes.find(p) != s_used_planes.end())
194                                 continue;
196                         if (p->plane_type() != PlaneType::Overlay)
197                                 continue;
199                         pinfo.plane = p;
200                 }
202                 if (!pinfo.plane)
203                         EXIT("Failed to find available plane");
204         }
206         s_used_planes.insert(pinfo.plane);
208         pinfo.w = stoul(sm[5]);
209         pinfo.h = stoul(sm[6]);
211         if (sm[3].matched)
212                 pinfo.x = stoul(sm[3]);
213         else
214                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
216         if (sm[4].matched)
217                 pinfo.y = stoul(sm[4]);
218         else
219                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
222 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
224         vector<DumbFramebuffer*> v;
226         for (unsigned i = 0; i < s_num_buffers; ++i)
227                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
229         return v;
232 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
234         unsigned w = def_w;
235         unsigned h = def_h;
236         PixelFormat format = PixelFormat::XRGB8888;
238         if (!fb_str.empty()) {
239                 // XXX the regexp is not quite correct
240                 // 400x400-NV12
241                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
243                 smatch sm;
244                 if (!regex_match(fb_str, sm, fb_re))
245                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
247                 if (sm[1].matched)
248                         w = stoul(sm[1]);
249                 if (sm[2].matched)
250                         h = stoul(sm[2]);
251                 if (sm[3].matched)
252                         format = FourCCToPixelFormat(sm[3]);
253         }
255         vector<DumbFramebuffer*> v;
257         for (unsigned i = 0; i < s_num_buffers; ++i)
258                 v.push_back(new DumbFramebuffer(card, w, h, format));
260         return v;
263 static const char* usage_str =
264                 "Usage: testpat [OPTION]...\n\n"
265                 "Show a test pattern on a display or plane\n\n"
266                 "Options:\n"
267                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
268                 "  -c, --connector=CONN      CONN is <connector>\n"
269                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
270                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
271                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
272                 "      --dmt                 Search for the given mode from DMT tables\n"
273                 "      --cea                 Search for the given mode from CEA tables\n"
274                 "      --flip                Do page flipping for each output\n"
275                 "      --sync                Synchronize page flipping\n"
276                 "\n"
277                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
278                 "<connector> can also be given by name.\n"
279                 "\n"
280                 "Options can be given multiple times to set up multiple displays or planes.\n"
281                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
282                 "an earlier option.\n"
283                 "If you omit parameters, testpat tries to guess what you mean\n"
284                 "\n"
285                 "Examples:\n"
286                 "\n"
287                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
288                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
289                 "XR24 framebuffer on first connected connector in the default mode:\n"
290                 "    testpat -f XR24\n\n"
291                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
292                 "    testpat -p 400x400 -f XR24\n\n"
293                 "Test pattern on the second connector with default mode:\n"
294                 "    testpat -c 1\n"
295                 ;
297 static void usage()
299         puts(usage_str);
302 enum class ObjectType
304         Connector,
305         Crtc,
306         Plane,
307         Framebuffer,
308 };
310 struct Arg
312         ObjectType type;
313         string arg;
314 };
316 static string s_device_path = "/dev/dri/card0";
318 static vector<Arg> parse_cmdline(int argc, char **argv)
320         vector<Arg> args;
322         OptionSet optionset = {
323                 Option("|device=",
324                 [&](string s)
325                 {
326                         s_device_path = s;
327                 }),
328                 Option("c|connector=",
329                 [&](string s)
330                 {
331                         args.push_back(Arg { ObjectType::Connector, s });
332                 }),
333                 Option("r|crtc=", [&](string s)
334                 {
335                         args.push_back(Arg { ObjectType::Crtc, s });
336                 }),
337                 Option("p|plane=", [&](string s)
338                 {
339                         args.push_back(Arg { ObjectType::Plane, s });
340                 }),
341                 Option("f|fb=", [&](string s)
342                 {
343                         args.push_back(Arg { ObjectType::Framebuffer, s });
344                 }),
345                 Option("|dmt", []()
346                 {
347                         s_use_dmt = true;
348                 }),
349                 Option("|cea", []()
350                 {
351                         s_use_cea = true;
352                 }),
353                 Option("|flip", []()
354                 {
355                         s_flip_mode = true;
356                         s_num_buffers = 2;
357                 }),
358                 Option("|sync", []()
359                 {
360                         s_flip_sync = true;
361                 }),
362                 Option("h|help", [&]()
363                 {
364                         usage();
365                         exit(-1);
366                 }),
367         };
369         optionset.parse(argc, argv);
371         if (optionset.params().size() > 0) {
372                 usage();
373                 exit(-1);
374         }
376         return args;
379 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
381         vector<OutputInfo> outputs;
383         if (output_args.size() == 0) {
384                 // no output args, show a pattern on all screens
385                 for (auto& pipe : card.get_connected_pipelines()) {
386                         OutputInfo output = { };
387                         output.connector = pipe.connector;
388                         output.crtc = pipe.crtc;
389                         output.mode = output.connector->get_default_mode();
391                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
393                         outputs.push_back(output);
394                 }
396                 return outputs;
397         }
399         OutputInfo* current_output = 0;
400         PlaneInfo* current_plane = 0;
402         for (auto& arg : output_args) {
403                 switch (arg.type) {
404                 case ObjectType::Connector:
405                 {
406                         outputs.push_back(OutputInfo { });
407                         current_output = &outputs.back();
409                         parse_connector(card, arg.arg, *current_output);
410                         current_plane = 0;
412                         break;
413                 }
415                 case ObjectType::Crtc:
416                 {
417                         if (!current_output) {
418                                 outputs.push_back(OutputInfo { });
419                                 current_output = &outputs.back();
420                         }
422                         if (!current_output->connector)
423                                 get_default_connector(card, *current_output);
425                         parse_crtc(card, arg.arg, *current_output);
427                         current_output->user_set_crtc = true;
429                         current_plane = 0;
431                         break;
432                 }
434                 case ObjectType::Plane:
435                 {
436                         if (!current_output) {
437                                 outputs.push_back(OutputInfo { });
438                                 current_output = &outputs.back();
439                         }
441                         if (!current_output->connector)
442                                 get_default_connector(card, *current_output);
444                         if (!current_output->crtc)
445                                 get_default_crtc(card, *current_output);
447                         current_output->planes.push_back(PlaneInfo { });
448                         current_plane = &current_output->planes.back();
450                         parse_plane(card, arg.arg, *current_output, *current_plane);
452                         break;
453                 }
455                 case ObjectType::Framebuffer:
456                 {
457                         if (!current_output) {
458                                 outputs.push_back(OutputInfo { });
459                                 current_output = &outputs.back();
460                         }
462                         if (!current_output->connector)
463                                 get_default_connector(card, *current_output);
465                         if (!current_output->crtc)
466                                 get_default_crtc(card, *current_output);
468                         int def_w, def_h;
470                         if (current_plane) {
471                                 def_w = current_plane->w;
472                                 def_h = current_plane->h;
473                         } else {
474                                 def_w = current_output->mode.hdisplay;
475                                 def_h = current_output->mode.vdisplay;
476                         }
478                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
480                         if (current_plane)
481                                 current_plane->fbs = fbs;
482                         else
483                                 current_output->fbs = fbs;
485                         break;
486                 }
487                 }
488         }
490         // create default framebuffers if needed
491         for (OutputInfo& o : outputs) {
492                 if (!o.crtc) {
493                         get_default_crtc(card, *current_output);
494                         o.user_set_crtc = true;
495                 }
497                 if (o.fbs.empty() && o.user_set_crtc)
498                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
500                 for (PlaneInfo &p : o.planes) {
501                         if (p.fbs.empty())
502                                 p.fbs = get_default_fb(card, p.w, p.h);
503                 }
504         }
506         return outputs;
509 static std::string videomode_to_string(const Videomode& mode)
511         unsigned hfp = mode.hsync_start - mode.hdisplay;
512         unsigned hsw = mode.hsync_end - mode.hsync_start;
513         unsigned hbp = mode.htotal - mode.hsync_end;
515         unsigned vfp = mode.vsync_start - mode.vdisplay;
516         unsigned vsw = mode.vsync_end - mode.vsync_start;
517         unsigned vbp = mode.vtotal - mode.vsync_end;
519         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
520         if (mode.flags & (1<<4)) // XXX interlace
521                 hz *= 2;
523         char buf[256];
525         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
526                 mode.clock / 1000.0,
527                 mode.hdisplay, hfp, hsw, hbp,
528                 mode.vdisplay, vfp, vsw, vbp,
529                 mode.vrefresh, hz);
531         return std::string(buf);
534 static void print_outputs(const vector<OutputInfo>& outputs)
536         for (unsigned i = 0; i < outputs.size(); ++i) {
537                 const OutputInfo& o = outputs[i];
539                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
540                        o.connector->fullname().c_str());
541                 printf("  Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
542                 if (o.primary_plane)
543                         printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
544                 printf(": %ux%u-%u (%s)\n",
545                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
546                        videomode_to_string(o.mode).c_str());
547                 if (!o.fbs.empty()) {
548                         auto fb = o.fbs[0];
549                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
550                                PixelFormatToFourCC(fb->format()).c_str());
551                 }
553                 for (unsigned j = 0; j < o.planes.size(); ++j) {
554                         const PlaneInfo& p = o.planes[j];
555                         auto fb = p.fbs[0];
556                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
557                                p.x, p.y, p.w, p.h);
558                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
559                                PixelFormatToFourCC(fb->format()).c_str());
560                 }
561         }
564 static void draw_test_patterns(const vector<OutputInfo>& outputs)
566         for (const OutputInfo& o : outputs) {
567                 for (auto fb : o.fbs)
568                         draw_test_pattern(*fb);
570                 for (const PlaneInfo& p : o.planes)
571                         for (auto fb : p.fbs)
572                                 draw_test_pattern(*fb);
573         }
576 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
578         for (const OutputInfo& o : outputs) {
579                 auto conn = o.connector;
580                 auto crtc = o.crtc;
582                 if (!o.fbs.empty()) {
583                         auto fb = o.fbs[0];
584                         int r = crtc->set_mode(conn, *fb, o.mode);
585                         if (r)
586                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
587                                        crtc->id(), strerror(-r));
588                 }
590                 for (const PlaneInfo& p : o.planes) {
591                         auto fb = p.fbs[0];
592                         int r = crtc->set_plane(p.plane, *fb,
593                                                 p.x, p.y, p.w, p.h,
594                                                 0, 0, fb->width(), fb->height());
595                         if (r)
596                                 printf("crtc->set_plane() failed for plane %u: %s\n",
597                                        p.plane->id(), strerror(-r));
598                 }
599         }
602 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
604         // Keep blobs here so that we keep ref to them until we have committed the req
605         vector<unique_ptr<Blob>> blobs;
607         AtomicReq req(card);
609         for (const OutputInfo& o : outputs) {
610                 auto conn = o.connector;
611                 auto crtc = o.crtc;
613                 if (!o.fbs.empty()) {
614                         auto fb = o.fbs[0];
616                         blobs.emplace_back(o.mode.to_blob(card));
617                         Blob* mode_blob = blobs.back().get();
619                         req.add(conn, {
620                                         { "CRTC_ID", crtc->id() },
621                                 });
623                         req.add(crtc, {
624                                         { "ACTIVE", 1 },
625                                         { "MODE_ID", mode_blob->id() },
626                                 });
628                         req.add(o.primary_plane, {
629                                         { "FB_ID", fb->id() },
630                                         { "CRTC_ID", crtc->id() },
631                                         { "SRC_X", 0 << 16 },
632                                         { "SRC_Y", 0 << 16 },
633                                         { "SRC_W", fb->width() << 16 },
634                                         { "SRC_H", fb->height() << 16 },
635                                         { "CRTC_X", 0 },
636                                         { "CRTC_Y", 0 },
637                                         { "CRTC_W", fb->width() },
638                                         { "CRTC_H", fb->height() },
639                                 });
640                 }
642                 for (const PlaneInfo& p : o.planes) {
643                         auto fb = p.fbs[0];
645                         req.add(p.plane, {
646                                         { "FB_ID", fb->id() },
647                                         { "CRTC_ID", crtc->id() },
648                                         { "SRC_X", 0 << 16 },
649                                         { "SRC_Y", 0 << 16 },
650                                         { "SRC_W", fb->width() << 16 },
651                                         { "SRC_H", fb->height() << 16 },
652                                         { "CRTC_X", p.x },
653                                         { "CRTC_Y", p.y },
654                                         { "CRTC_W", p.w },
655                                         { "CRTC_H", p.h },
656                                 });
657                 }
658         }
660         int r;
662         r = req.test(true);
663         if (r)
664                 EXIT("Atomic test failed: %d\n", r);
666         r = req.commit_sync(true);
667         if (r)
668                 EXIT("Atomic commit failed: %d\n", r);
671 class FlipState : private PageFlipHandlerBase
673 public:
674         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
675                 : m_card(card), m_name(name), m_outputs(outputs)
676         {
677         }
679         void start_flipping()
680         {
681                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
682                 m_slowest_frame = std::chrono::duration<float>::min();
683                 m_frame_num = 0;
684                 queue_next();
685         }
687 private:
688         void handle_page_flip(uint32_t frame, double time)
689         {
690                 m_frame_num++;
692                 auto now = std::chrono::steady_clock::now();
694                 std::chrono::duration<float> diff = now - m_prev_frame;
695                 if (diff > m_slowest_frame)
696                         m_slowest_frame = diff;
698                 if (m_frame_num  % 100 == 0) {
699                         std::chrono::duration<float> fsec = now - m_prev_print;
700                         printf("Connector %s: fps %f, slowest %.2f ms\n",
701                                m_name.c_str(),
702                                100.0 / fsec.count(),
703                                m_slowest_frame.count() * 1000);
704                         m_prev_print = now;
705                         m_slowest_frame = std::chrono::duration<float>::min();
706                 }
708                 m_prev_frame = now;
710                 queue_next();
711         }
713         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
714         {
715                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
716         }
718         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
719         {
720                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
721                 int new_xpos = get_bar_pos(fb, frame_num);
723                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
724                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
725         }
727         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
728         {
729                 unsigned cur = frame_num % s_num_buffers;
731                 if (!o.fbs.empty()) {
732                         auto fb = o.fbs[cur];
734                         draw_bar(fb, frame_num);
736                         req.add(o.primary_plane, {
737                                         { "FB_ID", fb->id() },
738                                 });
739                 }
741                 for (const PlaneInfo& p : o.planes) {
742                         auto fb = p.fbs[cur];
744                         draw_bar(fb, frame_num);
746                         req.add(p.plane, {
747                                         { "FB_ID", fb->id() },
748                                 });
749                 }
750         }
752         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
753         {
754                 unsigned cur = frame_num % s_num_buffers;
756                 if (!o.fbs.empty()) {
757                         auto fb = o.fbs[cur];
759                         draw_bar(fb, frame_num);
761                         int r = o.crtc->page_flip(*fb, this);
762                         ASSERT(r == 0);
763                 }
765                 for (const PlaneInfo& p : o.planes) {
766                         auto fb = p.fbs[cur];
768                         draw_bar(fb, frame_num);
770                         int r = o.crtc->set_plane(p.plane, *fb,
771                                                   p.x, p.y, p.w, p.h,
772                                                   0, 0, fb->width(), fb->height());
773                         ASSERT(r == 0);
774                 }
775         }
777         void queue_next()
778         {
779                 if (m_card.has_atomic()) {
780                         AtomicReq req(m_card);
782                         for (auto o : m_outputs)
783                                 do_flip_output(req, m_frame_num, *o);
785                         int r = req.commit(this);
786                         if (r)
787                                 EXIT("Flip commit failed: %d\n", r);
788                 } else {
789                         ASSERT(m_outputs.size() == 1);
790                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
791                 }
792         }
794         Card& m_card;
795         string m_name;
796         vector<const OutputInfo*> m_outputs;
797         unsigned m_frame_num;
799         chrono::steady_clock::time_point m_prev_print;
800         chrono::steady_clock::time_point m_prev_frame;
801         chrono::duration<float> m_slowest_frame;
803         static const unsigned bar_width = 20;
804         static const unsigned bar_speed = 8;
805 };
807 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
809         fd_set fds;
811         FD_ZERO(&fds);
813         int fd = card.fd();
815         vector<unique_ptr<FlipState>> flipstates;
817         if (!s_flip_sync) {
818                 for (const OutputInfo& o : outputs) {
819                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
820                         flipstates.push_back(move(fs));
821                 }
822         } else {
823                 vector<const OutputInfo*> ois;
825                 string name;
826                 for (const OutputInfo& o : outputs) {
827                         name += to_string(o.connector->idx()) + ",";
828                         ois.push_back(&o);
829                 }
831                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
832                 flipstates.push_back(move(fs));
833         }
835         for (unique_ptr<FlipState>& fs : flipstates)
836                 fs->start_flipping();
838         while (true) {
839                 int r;
841                 FD_SET(0, &fds);
842                 FD_SET(fd, &fds);
844                 r = select(fd + 1, &fds, NULL, NULL, NULL);
845                 if (r < 0) {
846                         fprintf(stderr, "select() failed with %d: %m\n", errno);
847                         break;
848                 } else if (FD_ISSET(0, &fds)) {
849                         fprintf(stderr, "Exit due to user-input\n");
850                         break;
851                 } else if (FD_ISSET(fd, &fds)) {
852                         card.call_page_flip_handlers();
853                 }
854         }
857 int main(int argc, char **argv)
859         vector<Arg> output_args = parse_cmdline(argc, argv);
861         Card card(s_device_path);
863         if (!card.has_atomic() && s_flip_sync)
864                 EXIT("Synchronized flipping requires atomic modesetting");
866         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
868         if (card.has_atomic()) {
869                 for (OutputInfo& o : outputs) {
870                         o.primary_plane = o.crtc->get_primary_plane();
872                         if (!o.fbs.empty() && !o.primary_plane)
873                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
874                 }
875         }
877         if (!s_flip_mode)
878                 draw_test_patterns(outputs);
880         print_outputs(outputs);
882         if (card.has_atomic())
883                 set_crtcs_n_planes(card, outputs);
884         else
885                 set_crtcs_n_planes_legacy(card, outputs);
887         printf("press enter to exit\n");
889         if (s_flip_mode)
890                 main_flip(card, outputs);
891         else
892                 getchar();