Support RGB888
[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++/kms++.h>
9 #include <kms++/modedb.h>
11 #include <kms++util/kms++util.h>
13 using namespace std;
14 using namespace kms;
16 struct PlaneInfo
17 {
18         Plane* plane;
20         unsigned x;
21         unsigned y;
22         unsigned w;
23         unsigned h;
25         vector<DumbFramebuffer*> fbs;
26 };
28 struct OutputInfo
29 {
30         Connector* connector;
32         Crtc* crtc;
33         Plane* primary_plane;
34         Videomode mode;
35         bool user_set_crtc;
36         vector<DumbFramebuffer*> fbs;
38         vector<PlaneInfo> planes;
39 };
41 static bool s_use_dmt;
42 static bool s_use_cea;
43 static unsigned s_num_buffers = 1;
44 static bool s_flip_mode;
45 static bool s_flip_sync;
47 static set<Crtc*> s_used_crtcs;
48 static set<Plane*> s_used_planes;
50 __attribute__ ((unused))
51 static void print_regex_match(smatch sm)
52 {
53         for (unsigned i = 0; i < sm.size(); ++i) {
54                 string str = sm[i].str();
55                 printf("%u: %s\n", i, str.c_str());
56         }
57 }
59 static void get_default_connector(Card& card, OutputInfo& output)
60 {
61         output.connector = card.get_first_connected_connector();
62         output.mode = output.connector->get_default_mode();
63 }
65 static void parse_connector(Card& card, const string& str, OutputInfo& output)
66 {
67         Connector* conn = resolve_connector(card, str);
69         if (!conn)
70                 EXIT("No connector '%s'", str.c_str());
72         if (!conn->connected())
73                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
75         output.connector = conn;
76         output.mode = output.connector->get_default_mode();
77 }
79 static void get_default_crtc(Card& card, OutputInfo& output)
80 {
81         Crtc* crtc = output.connector->get_current_crtc();
83         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
84                 s_used_crtcs.insert(crtc);
85                 output.crtc = crtc;
86                 return;
87         }
89         for (const auto& possible : output.connector->get_possible_crtcs()) {
90                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
91                         s_used_crtcs.insert(possible);
92                         output.crtc = possible;
93                         return;
94                 }
95         }
97         EXIT("Could not find available crtc");
98 }
100 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
102         // @12:1920x1200@60
103         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
105         smatch sm;
106         if (!regex_match(crtc_str, sm, mode_re))
107                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
109         if (sm[2].matched) {
110                 bool use_id = sm[1].length() == 1;
111                 unsigned num = stoul(sm[2].str());
113                 if (use_id) {
114                         Crtc* c = card.get_crtc(num);
115                         if (!c)
116                                 EXIT("Bad crtc id '%u'", num);
118                         output.crtc = c;
119                 } else {
120                         auto crtcs = card.get_crtcs();
122                         if (num >= crtcs.size())
123                                 EXIT("Bad crtc number '%u'", num);
125                         output.crtc = crtcs[num];
126                 }
127         } else {
128                 output.crtc = output.connector->get_current_crtc();
129         }
131         unsigned w = stoul(sm[3]);
132         unsigned h = stoul(sm[4]);
133         bool ilace = sm[5].matched ? true : false;
134         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
136         bool found_mode = false;
138         try {
139                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
140                 found_mode = true;
141         } catch (exception& e) { }
143         if (!found_mode && s_use_dmt) {
144                 try {
145                         output.mode = find_dmt(w, h, refresh, ilace);
146                         found_mode = true;
147                         printf("Found mode from DMT\n");
148                 } catch (exception& e) { }
149         }
151         if (!found_mode && s_use_cea) {
152                 try {
153                         output.mode = find_cea(w, h, refresh, ilace);
154                         found_mode = true;
155                         printf("Found mode from CEA\n");
156                 } catch (exception& e) { }
157         }
159         if (!found_mode)
160                 throw invalid_argument("Mode not found");
163 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
165         // 3:400,400-400x400
166         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
168         smatch sm;
169         if (!regex_match(plane_str, sm, plane_re))
170                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
172         if (sm[2].matched) {
173                 bool use_id = sm[1].length() == 1;
174                 unsigned num = stoul(sm[2].str());
176                 if (use_id) {
177                         Plane* p = card.get_plane(num);
178                         if (!p)
179                                 EXIT("Bad plane id '%u'", num);
181                         pinfo.plane = p;
182                 } else {
183                         auto planes = card.get_planes();
185                         if (num >= planes.size())
186                                 EXIT("Bad plane number '%u'", num);
188                         pinfo.plane = planes[num];
189                 }
190         } else {
191                 for (Plane* p : output.crtc->get_possible_planes()) {
192                         if (s_used_planes.find(p) != s_used_planes.end())
193                                 continue;
195                         if (p->plane_type() != PlaneType::Overlay)
196                                 continue;
198                         pinfo.plane = p;
199                 }
201                 if (!pinfo.plane)
202                         EXIT("Failed to find available plane");
203         }
205         s_used_planes.insert(pinfo.plane);
207         pinfo.w = stoul(sm[5]);
208         pinfo.h = stoul(sm[6]);
210         if (sm[3].matched)
211                 pinfo.x = stoul(sm[3]);
212         else
213                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
215         if (sm[4].matched)
216                 pinfo.y = stoul(sm[4]);
217         else
218                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
221 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
223         vector<DumbFramebuffer*> v;
225         for (unsigned i = 0; i < s_num_buffers; ++i)
226                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
228         return v;
231 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
233         unsigned w = def_w;
234         unsigned h = def_h;
235         PixelFormat format = PixelFormat::XRGB8888;
237         if (!fb_str.empty()) {
238                 // XXX the regexp is not quite correct
239                 // 400x400-NV12
240                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
242                 smatch sm;
243                 if (!regex_match(fb_str, sm, fb_re))
244                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
246                 if (sm[1].matched)
247                         w = stoul(sm[1]);
248                 if (sm[2].matched)
249                         h = stoul(sm[2]);
250                 if (sm[3].matched)
251                         format = FourCCToPixelFormat(sm[3]);
252         }
254         vector<DumbFramebuffer*> v;
256         for (unsigned i = 0; i < s_num_buffers; ++i)
257                 v.push_back(new DumbFramebuffer(card, w, h, format));
259         return v;
262 static const char* usage_str =
263                 "Usage: testpat [OPTION]...\n\n"
264                 "Show a test pattern on a display or plane\n\n"
265                 "Options:\n"
266                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
267                 "  -c, --connector=CONN      CONN is <connector>\n"
268                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
269                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
270                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
271                 "      --dmt                 Search for the given mode from DMT tables\n"
272                 "      --cea                 Search for the given mode from CEA tables\n"
273                 "      --flip                Do page flipping for each output\n"
274                 "      --sync                Synchronize page flipping\n"
275                 "\n"
276                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
277                 "<connector> can also be given by name.\n"
278                 "\n"
279                 "Options can be given multiple times to set up multiple displays or planes.\n"
280                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
281                 "an earlier option.\n"
282                 "If you omit parameters, testpat tries to guess what you mean\n"
283                 "\n"
284                 "Examples:\n"
285                 "\n"
286                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
287                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
288                 "XR24 framebuffer on first connected connector in the default mode:\n"
289                 "    testpat -f XR24\n\n"
290                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
291                 "    testpat -p 400x400 -f XR24\n\n"
292                 "Test pattern on the second connector with default mode:\n"
293                 "    testpat -c 1\n"
294                 ;
296 static void usage()
298         puts(usage_str);
301 enum class ObjectType
303         Connector,
304         Crtc,
305         Plane,
306         Framebuffer,
307 };
309 struct Arg
311         ObjectType type;
312         string arg;
313 };
315 static string s_device_path = "/dev/dri/card0";
317 static vector<Arg> parse_cmdline(int argc, char **argv)
319         vector<Arg> args;
321         OptionSet optionset = {
322                 Option("|device=",
323                 [&](string s)
324                 {
325                         s_device_path = s;
326                 }),
327                 Option("c|connector=",
328                 [&](string s)
329                 {
330                         args.push_back(Arg { ObjectType::Connector, s });
331                 }),
332                 Option("r|crtc=", [&](string s)
333                 {
334                         args.push_back(Arg { ObjectType::Crtc, s });
335                 }),
336                 Option("p|plane=", [&](string s)
337                 {
338                         args.push_back(Arg { ObjectType::Plane, s });
339                 }),
340                 Option("f|fb=", [&](string s)
341                 {
342                         args.push_back(Arg { ObjectType::Framebuffer, s });
343                 }),
344                 Option("|dmt", []()
345                 {
346                         s_use_dmt = true;
347                 }),
348                 Option("|cea", []()
349                 {
350                         s_use_cea = true;
351                 }),
352                 Option("|flip", []()
353                 {
354                         s_flip_mode = true;
355                         s_num_buffers = 2;
356                 }),
357                 Option("|sync", []()
358                 {
359                         s_flip_sync = true;
360                 }),
361                 Option("h|help", [&]()
362                 {
363                         usage();
364                         exit(-1);
365                 }),
366         };
368         optionset.parse(argc, argv);
370         if (optionset.params().size() > 0) {
371                 usage();
372                 exit(-1);
373         }
375         return args;
378 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
380         vector<OutputInfo> outputs;
382         if (output_args.size() == 0) {
383                 // no output args, show a pattern on all screens
384                 for (auto& pipe : card.get_connected_pipelines()) {
385                         OutputInfo output = { };
386                         output.connector = pipe.connector;
387                         output.crtc = pipe.crtc;
388                         output.mode = output.connector->get_default_mode();
390                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
392                         outputs.push_back(output);
393                 }
395                 return outputs;
396         }
398         OutputInfo* current_output = 0;
399         PlaneInfo* current_plane = 0;
401         for (auto& arg : output_args) {
402                 switch (arg.type) {
403                 case ObjectType::Connector:
404                 {
405                         outputs.push_back(OutputInfo { });
406                         current_output = &outputs.back();
408                         parse_connector(card, arg.arg, *current_output);
409                         current_plane = 0;
411                         break;
412                 }
414                 case ObjectType::Crtc:
415                 {
416                         if (!current_output) {
417                                 outputs.push_back(OutputInfo { });
418                                 current_output = &outputs.back();
419                         }
421                         if (!current_output->connector)
422                                 get_default_connector(card, *current_output);
424                         parse_crtc(card, arg.arg, *current_output);
426                         current_output->user_set_crtc = true;
428                         current_plane = 0;
430                         break;
431                 }
433                 case ObjectType::Plane:
434                 {
435                         if (!current_output) {
436                                 outputs.push_back(OutputInfo { });
437                                 current_output = &outputs.back();
438                         }
440                         if (!current_output->connector)
441                                 get_default_connector(card, *current_output);
443                         if (!current_output->crtc)
444                                 get_default_crtc(card, *current_output);
446                         current_output->planes.push_back(PlaneInfo { });
447                         current_plane = &current_output->planes.back();
449                         parse_plane(card, arg.arg, *current_output, *current_plane);
451                         break;
452                 }
454                 case ObjectType::Framebuffer:
455                 {
456                         if (!current_output) {
457                                 outputs.push_back(OutputInfo { });
458                                 current_output = &outputs.back();
459                         }
461                         if (!current_output->connector)
462                                 get_default_connector(card, *current_output);
464                         if (!current_output->crtc)
465                                 get_default_crtc(card, *current_output);
467                         int def_w, def_h;
469                         if (current_plane) {
470                                 def_w = current_plane->w;
471                                 def_h = current_plane->h;
472                         } else {
473                                 def_w = current_output->mode.hdisplay;
474                                 def_h = current_output->mode.vdisplay;
475                         }
477                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
479                         if (current_plane)
480                                 current_plane->fbs = fbs;
481                         else
482                                 current_output->fbs = fbs;
484                         break;
485                 }
486                 }
487         }
489         // create default framebuffers if needed
490         for (OutputInfo& o : outputs) {
491                 if (!o.crtc) {
492                         get_default_crtc(card, *current_output);
493                         o.user_set_crtc = true;
494                 }
496                 if (o.fbs.empty() && o.user_set_crtc)
497                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
499                 for (PlaneInfo &p : o.planes) {
500                         if (p.fbs.empty())
501                                 p.fbs = get_default_fb(card, p.w, p.h);
502                 }
503         }
505         return outputs;
508 static std::string videomode_to_string(const Videomode& mode)
510         unsigned hfp = mode.hsync_start - mode.hdisplay;
511         unsigned hsw = mode.hsync_end - mode.hsync_start;
512         unsigned hbp = mode.htotal - mode.hsync_end;
514         unsigned vfp = mode.vsync_start - mode.vdisplay;
515         unsigned vsw = mode.vsync_end - mode.vsync_start;
516         unsigned vbp = mode.vtotal - mode.vsync_end;
518         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
519         if (mode.flags & (1<<4)) // XXX interlace
520                 hz *= 2;
522         char buf[256];
524         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
525                 mode.clock / 1000.0,
526                 mode.hdisplay, hfp, hsw, hbp,
527                 mode.vdisplay, vfp, vsw, vbp,
528                 mode.vrefresh, hz);
530         return std::string(buf);
533 static void print_outputs(const vector<OutputInfo>& outputs)
535         for (unsigned i = 0; i < outputs.size(); ++i) {
536                 const OutputInfo& o = outputs[i];
538                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
539                        o.connector->fullname().c_str());
540                 printf("  Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
541                 if (o.primary_plane)
542                         printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
543                 printf(": %ux%u-%u (%s)\n",
544                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
545                        videomode_to_string(o.mode).c_str());
546                 if (!o.fbs.empty()) {
547                         auto fb = o.fbs[0];
548                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
549                                PixelFormatToFourCC(fb->format()).c_str());
550                 }
552                 for (unsigned j = 0; j < o.planes.size(); ++j) {
553                         const PlaneInfo& p = o.planes[j];
554                         auto fb = p.fbs[0];
555                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
556                                p.x, p.y, p.w, p.h);
557                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
558                                PixelFormatToFourCC(fb->format()).c_str());
559                 }
560         }
563 static void draw_test_patterns(const vector<OutputInfo>& outputs)
565         for (const OutputInfo& o : outputs) {
566                 for (auto fb : o.fbs)
567                         draw_test_pattern(*fb);
569                 for (const PlaneInfo& p : o.planes)
570                         for (auto fb : p.fbs)
571                                 draw_test_pattern(*fb);
572         }
575 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
577         for (const OutputInfo& o : outputs) {
578                 auto conn = o.connector;
579                 auto crtc = o.crtc;
581                 if (!o.fbs.empty()) {
582                         auto fb = o.fbs[0];
583                         int r = crtc->set_mode(conn, *fb, o.mode);
584                         if (r)
585                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
586                                        crtc->id(), strerror(-r));
587                 }
589                 for (const PlaneInfo& p : o.planes) {
590                         auto fb = p.fbs[0];
591                         int r = crtc->set_plane(p.plane, *fb,
592                                                 p.x, p.y, p.w, p.h,
593                                                 0, 0, fb->width(), fb->height());
594                         if (r)
595                                 printf("crtc->set_plane() failed for plane %u: %s\n",
596                                        p.plane->id(), strerror(-r));
597                 }
598         }
601 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
603         // Keep blobs here so that we keep ref to them until we have committed the req
604         vector<unique_ptr<Blob>> blobs;
606         AtomicReq req(card);
608         for (const OutputInfo& o : outputs) {
609                 auto conn = o.connector;
610                 auto crtc = o.crtc;
612                 if (!o.fbs.empty()) {
613                         auto fb = o.fbs[0];
615                         blobs.emplace_back(o.mode.to_blob(card));
616                         Blob* mode_blob = blobs.back().get();
618                         req.add(conn, {
619                                         { "CRTC_ID", crtc->id() },
620                                 });
622                         req.add(crtc, {
623                                         { "ACTIVE", 1 },
624                                         { "MODE_ID", mode_blob->id() },
625                                 });
627                         req.add(o.primary_plane, {
628                                         { "FB_ID", fb->id() },
629                                         { "CRTC_ID", crtc->id() },
630                                         { "SRC_X", 0 << 16 },
631                                         { "SRC_Y", 0 << 16 },
632                                         { "SRC_W", fb->width() << 16 },
633                                         { "SRC_H", fb->height() << 16 },
634                                         { "CRTC_X", 0 },
635                                         { "CRTC_Y", 0 },
636                                         { "CRTC_W", fb->width() },
637                                         { "CRTC_H", fb->height() },
638                                 });
639                 }
641                 for (const PlaneInfo& p : o.planes) {
642                         auto fb = p.fbs[0];
644                         req.add(p.plane, {
645                                         { "FB_ID", fb->id() },
646                                         { "CRTC_ID", crtc->id() },
647                                         { "SRC_X", 0 << 16 },
648                                         { "SRC_Y", 0 << 16 },
649                                         { "SRC_W", fb->width() << 16 },
650                                         { "SRC_H", fb->height() << 16 },
651                                         { "CRTC_X", p.x },
652                                         { "CRTC_Y", p.y },
653                                         { "CRTC_W", p.w },
654                                         { "CRTC_H", p.h },
655                                 });
656                 }
657         }
659         int r;
661         r = req.test(true);
662         if (r)
663                 EXIT("Atomic test failed: %d\n", r);
665         r = req.commit_sync(true);
666         if (r)
667                 EXIT("Atomic commit failed: %d\n", r);
670 class FlipState : private PageFlipHandlerBase
672 public:
673         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
674                 : m_card(card), m_name(name), m_outputs(outputs)
675         {
676         }
678         void start_flipping()
679         {
680                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
681                 m_slowest_frame = std::chrono::duration<float>::min();
682                 m_frame_num = 0;
683                 queue_next();
684         }
686 private:
687         void handle_page_flip(uint32_t frame, double time)
688         {
689                 m_frame_num++;
691                 auto now = std::chrono::steady_clock::now();
693                 std::chrono::duration<float> diff = now - m_prev_frame;
694                 if (diff > m_slowest_frame)
695                         m_slowest_frame = diff;
697                 if (m_frame_num  % 100 == 0) {
698                         std::chrono::duration<float> fsec = now - m_prev_print;
699                         printf("Connector %s: fps %f, slowest %.2f ms\n",
700                                m_name.c_str(),
701                                100.0 / fsec.count(),
702                                m_slowest_frame.count() * 1000);
703                         m_prev_print = now;
704                         m_slowest_frame = std::chrono::duration<float>::min();
705                 }
707                 m_prev_frame = now;
709                 queue_next();
710         }
712         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
713         {
714                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
715         }
717         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
718         {
719                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
720                 int new_xpos = get_bar_pos(fb, frame_num);
722                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
723                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
724         }
726         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
727         {
728                 unsigned cur = frame_num % s_num_buffers;
730                 if (!o.fbs.empty()) {
731                         auto fb = o.fbs[cur];
733                         draw_bar(fb, frame_num);
735                         req.add(o.primary_plane, {
736                                         { "FB_ID", fb->id() },
737                                 });
738                 }
740                 for (const PlaneInfo& p : o.planes) {
741                         auto fb = p.fbs[cur];
743                         draw_bar(fb, frame_num);
745                         req.add(p.plane, {
746                                         { "FB_ID", fb->id() },
747                                 });
748                 }
749         }
751         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
752         {
753                 unsigned cur = frame_num % s_num_buffers;
755                 if (!o.fbs.empty()) {
756                         auto fb = o.fbs[cur];
758                         draw_bar(fb, frame_num);
760                         int r = o.crtc->page_flip(*fb, this);
761                         ASSERT(r == 0);
762                 }
764                 for (const PlaneInfo& p : o.planes) {
765                         auto fb = p.fbs[cur];
767                         draw_bar(fb, frame_num);
769                         int r = o.crtc->set_plane(p.plane, *fb,
770                                                   p.x, p.y, p.w, p.h,
771                                                   0, 0, fb->width(), fb->height());
772                         ASSERT(r == 0);
773                 }
774         }
776         void queue_next()
777         {
778                 if (m_card.has_atomic()) {
779                         AtomicReq req(m_card);
781                         for (auto o : m_outputs)
782                                 do_flip_output(req, m_frame_num, *o);
784                         int r = req.commit(this);
785                         if (r)
786                                 EXIT("Flip commit failed: %d\n", r);
787                 } else {
788                         ASSERT(m_outputs.size() == 1);
789                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
790                 }
791         }
793         Card& m_card;
794         string m_name;
795         vector<const OutputInfo*> m_outputs;
796         unsigned m_frame_num;
798         chrono::steady_clock::time_point m_prev_print;
799         chrono::steady_clock::time_point m_prev_frame;
800         chrono::duration<float> m_slowest_frame;
802         static const unsigned bar_width = 20;
803         static const unsigned bar_speed = 8;
804 };
806 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
808         fd_set fds;
810         FD_ZERO(&fds);
812         int fd = card.fd();
814         vector<unique_ptr<FlipState>> flipstates;
816         if (!s_flip_sync) {
817                 for (const OutputInfo& o : outputs) {
818                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
819                         flipstates.push_back(move(fs));
820                 }
821         } else {
822                 vector<const OutputInfo*> ois;
824                 string name;
825                 for (const OutputInfo& o : outputs) {
826                         name += to_string(o.connector->idx()) + ",";
827                         ois.push_back(&o);
828                 }
830                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
831                 flipstates.push_back(move(fs));
832         }
834         for (unique_ptr<FlipState>& fs : flipstates)
835                 fs->start_flipping();
837         while (true) {
838                 int r;
840                 FD_SET(0, &fds);
841                 FD_SET(fd, &fds);
843                 r = select(fd + 1, &fds, NULL, NULL, NULL);
844                 if (r < 0) {
845                         fprintf(stderr, "select() failed with %d: %m\n", errno);
846                         break;
847                 } else if (FD_ISSET(0, &fds)) {
848                         fprintf(stderr, "Exit due to user-input\n");
849                         break;
850                 } else if (FD_ISSET(fd, &fds)) {
851                         card.call_page_flip_handlers();
852                 }
853         }
856 int main(int argc, char **argv)
858         vector<Arg> output_args = parse_cmdline(argc, argv);
860         Card card(s_device_path);
862         if (!card.has_atomic() && s_flip_sync)
863                 EXIT("Synchronized flipping requires atomic modesetting");
865         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
867         if (card.has_atomic()) {
868                 for (OutputInfo& o : outputs) {
869                         o.primary_plane = o.crtc->get_primary_plane();
871                         if (!o.fbs.empty() && !o.primary_plane)
872                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
873                 }
874         }
876         if (!s_flip_mode)
877                 draw_test_patterns(outputs);
879         print_outputs(outputs);
881         if (card.has_atomic())
882                 set_crtcs_n_planes(card, outputs);
883         else
884                 set_crtcs_n_planes_legacy(card, outputs);
886         printf("press enter to exit\n");
888         if (s_flip_mode)
889                 main_flip(card, outputs);
890         else
891                 getchar();