testpat: add page flipping mode
[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 = nullptr;
70         auto connectors = card.get_connectors();
72         if (str[0] == '@') {
73                 char* endptr;
74                 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
75                 if (*endptr == 0) {
76                         if (idx >= connectors.size())
77                                 EXIT("Bad connector number '%u'", idx);
79                         conn = connectors[idx];
80                 }
81         } else {
82                 char* endptr;
83                 unsigned id = strtoul(str.c_str(), &endptr, 10);
84                 if (*endptr == 0) {
85                         Connector* c = card.get_connector(id);
86                         if (!c)
87                                 EXIT("Bad connector id '%u'", id);
89                         conn = c;
90                 }
91         }
93         if (!conn) {
94                 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
95                 if (iter != connectors.end())
96                         conn = *iter;
97         }
99         if (!conn)
100                 EXIT("No connector '%s'", str.c_str());
102         if (!conn->connected())
103                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
105         output.connector = conn;
106         output.mode = output.connector->get_default_mode();
109 static void get_default_crtc(Card& card, OutputInfo& output)
111         Crtc* crtc = output.connector->get_current_crtc();
113         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
114                 s_used_crtcs.insert(crtc);
115                 output.crtc = crtc;
116                 return;
117         }
119         for (const auto& possible : output.connector->get_possible_crtcs()) {
120                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
121                         s_used_crtcs.insert(possible);
122                         output.crtc = possible;
123                         return;
124                 }
125         }
127         EXIT("Could not find available crtc");
130 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
132         // @12:1920x1200@60
133         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
135         smatch sm;
136         if (!regex_match(crtc_str, sm, mode_re))
137                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
139         if (sm[2].matched) {
140                 bool use_idx = sm[1].length() == 1;
141                 unsigned num = stoul(sm[2].str());
143                 if (use_idx) {
144                         auto crtcs = card.get_crtcs();
146                         if (num >= crtcs.size())
147                                 EXIT("Bad crtc number '%u'", num);
149                         output.crtc = crtcs[num];
150                 } else {
151                         Crtc* c = card.get_crtc(num);
152                         if (!c)
153                                 EXIT("Bad crtc id '%u'", num);
155                         output.crtc = c;
156                 }
157         } else {
158                 output.crtc = output.connector->get_current_crtc();
159         }
161         unsigned w = stoul(sm[3]);
162         unsigned h = stoul(sm[4]);
163         bool ilace = sm[5].matched ? true : false;
164         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
166         bool found_mode = false;
168         try {
169                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
170                 found_mode = true;
171         } catch (exception& e) { }
173         if (!found_mode && s_use_dmt) {
174                 try {
175                         output.mode = find_dmt(w, h, refresh, ilace);
176                         found_mode = true;
177                         printf("Found mode from DMT\n");
178                 } catch (exception& e) { }
179         }
181         if (!found_mode && s_use_cea) {
182                 try {
183                         output.mode = find_cea(w, h, refresh, ilace);
184                         found_mode = true;
185                         printf("Found mode from CEA\n");
186                 } catch (exception& e) { }
187         }
189         if (!found_mode)
190                 throw invalid_argument("Mode not found");
193 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
195         // 3:400,400-400x400
196         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
198         smatch sm;
199         if (!regex_match(plane_str, sm, plane_re))
200                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
202         if (sm[2].matched) {
203                 bool use_idx = sm[1].length() == 1;
204                 unsigned num = stoul(sm[2].str());
206                 if (use_idx) {
207                         auto planes = card.get_planes();
209                         if (num >= planes.size())
210                                 EXIT("Bad plane number '%u'", num);
212                         pinfo.plane = planes[num];
213                 } else {
214                         Plane* p = card.get_plane(num);
215                         if (!p)
216                                 EXIT("Bad plane id '%u'", num);
218                         pinfo.plane = p;
219                 }
220         } else {
221                 for (Plane* p : output.crtc->get_possible_planes()) {
222                         if (s_used_planes.find(p) != s_used_planes.end())
223                                 continue;
225                         if (p->plane_type() != PlaneType::Overlay)
226                                 continue;
228                         pinfo.plane = p;
229                 }
231                 if (!pinfo.plane)
232                         EXIT("Failed to find available plane");
233         }
235         s_used_planes.insert(pinfo.plane);
237         pinfo.w = stoul(sm[5]);
238         pinfo.h = stoul(sm[6]);
240         if (sm[3].matched)
241                 pinfo.x = stoul(sm[3]);
242         else
243                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
245         if (sm[4].matched)
246                 pinfo.y = stoul(sm[4]);
247         else
248                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
251 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
253         vector<DumbFramebuffer*> v;
255         for (unsigned i = 0; i < s_num_buffers; ++i)
256                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
258         return v;
261 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
263         unsigned w = def_w;
264         unsigned h = def_h;
265         PixelFormat format = PixelFormat::XRGB8888;
267         if (!fb_str.empty()) {
268                 // XXX the regexp is not quite correct
269                 // 400x400-NV12
270                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
272                 smatch sm;
273                 if (!regex_match(fb_str, sm, fb_re))
274                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
276                 if (sm[1].matched)
277                         w = stoul(sm[1]);
278                 if (sm[2].matched)
279                         h = stoul(sm[2]);
280                 if (sm[3].matched)
281                         format = FourCCToPixelFormat(sm[3]);
282         }
284         vector<DumbFramebuffer*> v;
286         for (unsigned i = 0; i < s_num_buffers; ++i)
287                 v.push_back(new DumbFramebuffer(card, w, h, format));
289         return v;
292 static const char* usage_str =
293                 "Usage: testpat [OPTION]...\n\n"
294                 "Show a test pattern on a display or plane\n\n"
295                 "Options:\n"
296                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
297                 "  -c, --connector=CONN      CONN is <connector>\n"
298                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
299                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
300                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
301                 "      --dmt                 Search for the given mode from DMT tables\n"
302                 "      --cea                 Search for the given mode from CEA tables\n"
303                 "      --flip                Do page flipping for each output\n"
304                 "      --sync                Synchronize page flipping\n"
305                 "\n"
306                 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
307                 "<connector> can also be given by name.\n"
308                 "\n"
309                 "Options can be given multiple times to set up multiple displays or planes.\n"
310                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
311                 "an earlier option.\n"
312                 "If you omit parameters, testpat tries to guess what you mean\n"
313                 "\n"
314                 "Examples:\n"
315                 "\n"
316                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
317                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
318                 "XR24 framebuffer on first connected connector in the default mode:\n"
319                 "    testpat -f XR24\n\n"
320                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
321                 "    testpat -p 400x400 -f XR24\n\n"
322                 "Test pattern on the second connector with default mode:\n"
323                 "    testpat -c @1\n"
324                 ;
326 static void usage()
328         puts(usage_str);
331 enum class ObjectType
333         Connector,
334         Crtc,
335         Plane,
336         Framebuffer,
337 };
339 struct Arg
341         ObjectType type;
342         string arg;
343 };
345 static string s_device_path = "/dev/dri/card0";
347 static vector<Arg> parse_cmdline(int argc, char **argv)
349         vector<Arg> args;
351         OptionSet optionset = {
352                 Option("|device=",
353                 [&](string s)
354                 {
355                         s_device_path = s;
356                 }),
357                 Option("c|connector=",
358                 [&](string s)
359                 {
360                         args.push_back(Arg { ObjectType::Connector, s });
361                 }),
362                 Option("r|crtc=", [&](string s)
363                 {
364                         args.push_back(Arg { ObjectType::Crtc, s });
365                 }),
366                 Option("p|plane=", [&](string s)
367                 {
368                         args.push_back(Arg { ObjectType::Plane, s });
369                 }),
370                 Option("f|fb=", [&](string s)
371                 {
372                         args.push_back(Arg { ObjectType::Framebuffer, s });
373                 }),
374                 Option("|dmt", []()
375                 {
376                         s_use_dmt = true;
377                 }),
378                 Option("|cea", []()
379                 {
380                         s_use_cea = true;
381                 }),
382                 Option("|flip", []()
383                 {
384                         s_flip_mode = true;
385                         s_num_buffers = 2;
386                 }),
387                 Option("|sync", []()
388                 {
389                         s_flip_sync = true;
390                 }),
391                 Option("h|help", [&]()
392                 {
393                         usage();
394                         exit(-1);
395                 }),
396         };
398         optionset.parse(argc, argv);
400         if (optionset.params().size() > 0) {
401                 usage();
402                 exit(-1);
403         }
405         return args;
408 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
410         vector<OutputInfo> outputs;
412         if (output_args.size() == 0) {
413                 // no output args, show a pattern on all screens
414                 for (auto& pipe : card.get_connected_pipelines()) {
415                         OutputInfo output = { };
416                         output.connector = pipe.connector;
417                         output.crtc = pipe.crtc;
418                         output.mode = output.connector->get_default_mode();
420                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
422                         outputs.push_back(output);
423                 }
425                 return outputs;
426         }
428         OutputInfo* current_output = 0;
429         PlaneInfo* current_plane = 0;
431         for (auto& arg : output_args) {
432                 switch (arg.type) {
433                 case ObjectType::Connector:
434                 {
435                         outputs.push_back(OutputInfo { });
436                         current_output = &outputs.back();
438                         parse_connector(card, arg.arg, *current_output);
439                         current_plane = 0;
441                         break;
442                 }
444                 case ObjectType::Crtc:
445                 {
446                         if (!current_output) {
447                                 outputs.push_back(OutputInfo { });
448                                 current_output = &outputs.back();
449                         }
451                         if (!current_output->connector)
452                                 get_default_connector(card, *current_output);
454                         parse_crtc(card, arg.arg, *current_output);
456                         current_output->user_set_crtc = true;
458                         current_plane = 0;
460                         break;
461                 }
463                 case ObjectType::Plane:
464                 {
465                         if (!current_output) {
466                                 outputs.push_back(OutputInfo { });
467                                 current_output = &outputs.back();
468                         }
470                         if (!current_output->connector)
471                                 get_default_connector(card, *current_output);
473                         if (!current_output->crtc)
474                                 get_default_crtc(card, *current_output);
476                         current_output->planes.push_back(PlaneInfo { });
477                         current_plane = &current_output->planes.back();
479                         parse_plane(card, arg.arg, *current_output, *current_plane);
481                         break;
482                 }
484                 case ObjectType::Framebuffer:
485                 {
486                         if (!current_output) {
487                                 outputs.push_back(OutputInfo { });
488                                 current_output = &outputs.back();
489                         }
491                         if (!current_output->connector)
492                                 get_default_connector(card, *current_output);
494                         if (!current_output->crtc)
495                                 get_default_crtc(card, *current_output);
497                         int def_w, def_h;
499                         if (current_plane) {
500                                 def_w = current_plane->w;
501                                 def_h = current_plane->h;
502                         } else {
503                                 def_w = current_output->mode.hdisplay;
504                                 def_h = current_output->mode.vdisplay;
505                         }
507                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
509                         if (current_plane)
510                                 current_plane->fbs = fbs;
511                         else
512                                 current_output->fbs = fbs;
514                         break;
515                 }
516                 }
517         }
519         // create default framebuffers if needed
520         for (OutputInfo& o : outputs) {
521                 if (!o.crtc) {
522                         get_default_crtc(card, *current_output);
523                         o.user_set_crtc = true;
524                 }
526                 if (o.fbs.empty() && o.user_set_crtc)
527                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
529                 for (PlaneInfo &p : o.planes) {
530                         if (p.fbs.empty())
531                                 p.fbs = get_default_fb(card, p.w, p.h);
532                 }
533         }
535         return outputs;
538 static std::string videomode_to_string(const Videomode& mode)
540         unsigned hfp = mode.hsync_start - mode.hdisplay;
541         unsigned hsw = mode.hsync_end - mode.hsync_start;
542         unsigned hbp = mode.htotal - mode.hsync_end;
544         unsigned vfp = mode.vsync_start - mode.vdisplay;
545         unsigned vsw = mode.vsync_end - mode.vsync_start;
546         unsigned vbp = mode.vtotal - mode.vsync_end;
548         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
549         if (mode.flags & (1<<4)) // XXX interlace
550                 hz *= 2;
552         char buf[256];
554         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
555                 mode.clock / 1000.0,
556                 mode.hdisplay, hfp, hsw, hbp,
557                 mode.vdisplay, vfp, vsw, vbp,
558                 mode.vrefresh, hz);
560         return std::string(buf);
563 static void print_outputs(const vector<OutputInfo>& outputs)
565         for (unsigned i = 0; i < outputs.size(); ++i) {
566                 const OutputInfo& o = outputs[i];
568                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
569                        o.connector->fullname().c_str());
570                 printf("  Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
571                 if (o.primary_plane)
572                         printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
573                 printf(": %ux%u-%u (%s)\n",
574                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
575                        videomode_to_string(o.mode).c_str());
576                 if (!o.fbs.empty()) {
577                         auto fb = o.fbs[0];
578                         printf("    Fb %ux%u-%s\n", fb->width(), fb->height(),
579                                PixelFormatToFourCC(fb->format()).c_str());
580                 }
582                 for (unsigned j = 0; j < o.planes.size(); ++j) {
583                         const PlaneInfo& p = o.planes[j];
584                         auto fb = p.fbs[0];
585                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
586                                p.x, p.y, p.w, p.h);
587                         printf("    Fb %ux%u-%s\n", fb->width(), fb->height(),
588                                PixelFormatToFourCC(fb->format()).c_str());
589                 }
590         }
593 static void draw_test_patterns(const vector<OutputInfo>& outputs)
595         for (const OutputInfo& o : outputs) {
596                 for (auto fb : o.fbs)
597                         draw_test_pattern(*fb);
599                 for (const PlaneInfo& p : o.planes)
600                         for (auto fb : p.fbs)
601                                 draw_test_pattern(*fb);
602         }
605 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
607         for (const OutputInfo& o : outputs) {
608                 auto conn = o.connector;
609                 auto crtc = o.crtc;
611                 if (!o.fbs.empty()) {
612                         auto fb = o.fbs[0];
613                         int r = crtc->set_mode(conn, *fb, o.mode);
614                         if (r)
615                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
616                                        crtc->id(), strerror(-r));
617                 }
619                 for (const PlaneInfo& p : o.planes) {
620                         auto fb = p.fbs[0];
621                         int r = crtc->set_plane(p.plane, *fb,
622                                                 p.x, p.y, p.w, p.h,
623                                                 0, 0, fb->width(), fb->height());
624                         if (r)
625                                 printf("crtc->set_plane() failed for plane %u: %s\n",
626                                        p.plane->id(), strerror(-r));
627                 }
628         }
631 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
633         // Keep blobs here so that we keep ref to them until we have committed the req
634         vector<unique_ptr<Blob>> blobs;
636         AtomicReq req(card);
638         for (const OutputInfo& o : outputs) {
639                 auto conn = o.connector;
640                 auto crtc = o.crtc;
642                 if (!o.fbs.empty()) {
643                         auto fb = o.fbs[0];
645                         blobs.emplace_back(o.mode.to_blob(card));
646                         Blob* mode_blob = blobs.back().get();
648                         req.add(conn, {
649                                         { "CRTC_ID", crtc->id() },
650                                 });
652                         req.add(crtc, {
653                                         { "ACTIVE", 1 },
654                                         { "MODE_ID", mode_blob->id() },
655                                 });
657                         req.add(o.primary_plane, {
658                                         { "FB_ID", fb->id() },
659                                         { "CRTC_ID", crtc->id() },
660                                         { "SRC_X", 0 << 16 },
661                                         { "SRC_Y", 0 << 16 },
662                                         { "SRC_W", fb->width() << 16 },
663                                         { "SRC_H", fb->height() << 16 },
664                                         { "CRTC_X", 0 },
665                                         { "CRTC_Y", 0 },
666                                         { "CRTC_W", fb->width() },
667                                         { "CRTC_H", fb->height() },
668                                 });
669                 }
671                 for (const PlaneInfo& p : o.planes) {
672                         auto fb = p.fbs[0];
674                         req.add(p.plane, {
675                                         { "FB_ID", fb->id() },
676                                         { "CRTC_ID", crtc->id() },
677                                         { "SRC_X", 0 << 16 },
678                                         { "SRC_Y", 0 << 16 },
679                                         { "SRC_W", fb->width() << 16 },
680                                         { "SRC_H", fb->height() << 16 },
681                                         { "CRTC_X", p.x },
682                                         { "CRTC_Y", p.y },
683                                         { "CRTC_W", p.w },
684                                         { "CRTC_H", p.h },
685                                 });
686                 }
687         }
689         int r;
691         r = req.test(true);
692         if (r)
693                 EXIT("Atomic test failed: %d\n", r);
695         r = req.commit_sync(true);
696         if (r)
697                 EXIT("Atomic commit failed: %d\n", r);
700 class FlipState : private PageFlipHandlerBase
702 public:
703         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
704                 : m_card(card), m_name(name), m_outputs(outputs)
705         {
706         }
708         void start_flipping()
709         {
710                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
711                 m_slowest_frame = std::chrono::duration<float>::min();
712                 m_frame_num = 0;
713                 queue_next();
714         }
716 private:
717         void handle_page_flip(uint32_t frame, double time)
718         {
719                 m_frame_num++;
721                 auto now = std::chrono::steady_clock::now();
723                 std::chrono::duration<float> diff = now - m_prev_frame;
724                 if (diff > m_slowest_frame)
725                         m_slowest_frame = diff;
727                 if (m_frame_num  % 100 == 0) {
728                         std::chrono::duration<float> fsec = now - m_prev_print;
729                         printf("Connector %s: fps %f, slowest %.2f ms\n",
730                                m_name.c_str(),
731                                100.0 / fsec.count(),
732                                m_slowest_frame.count() * 1000);
733                         m_prev_print = now;
734                         m_slowest_frame = std::chrono::duration<float>::min();
735                 }
737                 m_prev_frame = now;
739                 queue_next();
740         }
742         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
743         {
744                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
745         }
747         static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
748         {
749                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
750                 int new_xpos = get_bar_pos(fb, frame_num);
752                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
753         }
755         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
756         {
757                 unsigned cur = frame_num % s_num_buffers;
759                 if (!o.fbs.empty()) {
760                         auto fb = o.fbs[cur];
762                         draw_bar(fb, frame_num);
764                         req.add(o.primary_plane, {
765                                         { "FB_ID", fb->id() },
766                                 });
767                 }
769                 for (const PlaneInfo& p : o.planes) {
770                         auto fb = p.fbs[cur];
772                         draw_bar(fb, frame_num);
774                         req.add(p.plane, {
775                                         { "FB_ID", fb->id() },
776                                 });
777                 }
778         }
780         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
781         {
782                 unsigned cur = frame_num % s_num_buffers;
784                 if (!o.fbs.empty()) {
785                         auto fb = o.fbs[cur];
787                         draw_bar(fb, frame_num);
789                         int r = o.crtc->page_flip(*fb, this);
790                         ASSERT(r == 0);
791                 }
793                 for (const PlaneInfo& p : o.planes) {
794                         auto fb = p.fbs[cur];
796                         draw_bar(fb, frame_num);
798                         int r = o.crtc->set_plane(p.plane, *fb,
799                                                   p.x, p.y, p.w, p.h,
800                                                   0, 0, fb->width(), fb->height());
801                         ASSERT(r == 0);
802                 }
803         }
805         void queue_next()
806         {
807                 if (m_card.has_atomic()) {
808                         AtomicReq req(m_card);
810                         for (auto o : m_outputs)
811                                 do_flip_output(req, m_frame_num, *o);
813                         int r = req.commit(this);
814                         if (r)
815                                 EXIT("Flip commit failed: %d\n", r);
816                 } else {
817                         ASSERT(m_outputs.size() == 1);
818                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
819                 }
820         }
822         Card& m_card;
823         string m_name;
824         vector<const OutputInfo*> m_outputs;
825         unsigned m_frame_num;
827         chrono::steady_clock::time_point m_prev_print;
828         chrono::steady_clock::time_point m_prev_frame;
829         chrono::duration<float> m_slowest_frame;
831         static const unsigned bar_width = 20;
832         static const unsigned bar_speed = 8;
833 };
835 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
837         fd_set fds;
839         FD_ZERO(&fds);
841         int fd = card.fd();
843         vector<unique_ptr<FlipState>> flipstates;
845         if (!s_flip_sync) {
846                 for (const OutputInfo& o : outputs) {
847                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
848                         flipstates.push_back(move(fs));
849                 }
850         } else {
851                 vector<const OutputInfo*> ois;
853                 string name;
854                 for (const OutputInfo& o : outputs) {
855                         name += to_string(o.connector->idx()) + ",";
856                         ois.push_back(&o);
857                 }
859                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
860                 flipstates.push_back(move(fs));
861         }
863         for (unique_ptr<FlipState>& fs : flipstates)
864                 fs->start_flipping();
866         while (true) {
867                 int r;
869                 FD_SET(0, &fds);
870                 FD_SET(fd, &fds);
872                 r = select(fd + 1, &fds, NULL, NULL, NULL);
873                 if (r < 0) {
874                         fprintf(stderr, "select() failed with %d: %m\n", errno);
875                         break;
876                 } else if (FD_ISSET(0, &fds)) {
877                         fprintf(stderr, "Exit due to user-input\n");
878                         break;
879                 } else if (FD_ISSET(fd, &fds)) {
880                         card.call_page_flip_handlers();
881                 }
882         }
885 int main(int argc, char **argv)
887         vector<Arg> output_args = parse_cmdline(argc, argv);
889         Card card(s_device_path);
891         if (!card.has_atomic() && s_flip_sync)
892                 EXIT("Synchronized flipping requires atomic modesetting");
894         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
896         if (card.has_atomic()) {
897                 for (OutputInfo& o : outputs) {
898                         o.primary_plane = o.crtc->get_primary_plane();
900                         if (!o.fbs.empty() && !o.primary_plane)
901                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
902                 }
903         }
905         if (!s_flip_mode)
906                 draw_test_patterns(outputs);
908         print_outputs(outputs);
910         if (card.has_atomic())
911                 set_crtcs_n_planes(card, outputs);
912         else
913                 set_crtcs_n_planes_legacy(card, outputs);
915         printf("press enter to exit\n");
917         if (s_flip_mode)
918                 main_flip(card, outputs);
919         else
920                 getchar();