ba7d7906d7955d511a42838e00b612887168f91a
[android/external-libkmsxx.git] / utils / kmstest.cpp
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
8 #include <sys/select.h>
10 #include <kms++/kms++.h>
11 #include <kms++/modedb.h>
12 #include <kms++/mode_cvt.h>
14 #include <kms++util/kms++util.h>
16 using namespace std;
17 using namespace kms;
19 struct PlaneInfo
20 {
21         Plane* plane;
23         unsigned x;
24         unsigned y;
25         unsigned w;
26         unsigned h;
28         unsigned view_x;
29         unsigned view_y;
30         unsigned view_w;
31         unsigned view_h;
33         vector<MappedFramebuffer*> fbs;
34 };
36 struct OutputInfo
37 {
38         Connector* connector;
40         Crtc* crtc;
41         Plane* primary_plane;
42         Videomode mode;
43         bool user_set_crtc;
44         vector<MappedFramebuffer*> fbs;
46         vector<PlaneInfo> planes;
47 };
49 static bool s_use_dmt;
50 static bool s_use_cea;
51 static unsigned s_num_buffers = 1;
52 static bool s_flip_mode;
53 static bool s_flip_sync;
54 static bool s_cvt;
55 static bool s_cvt_v2;
56 static bool s_cvt_vid_opt;
57 static unsigned s_max_flips;
59 static set<Crtc*> s_used_crtcs;
60 static set<Plane*> s_used_planes;
62 __attribute__ ((unused))
63 static void print_regex_match(smatch sm)
64 {
65         for (unsigned i = 0; i < sm.size(); ++i) {
66                 string str = sm[i].str();
67                 printf("%u: %s\n", i, str.c_str());
68         }
69 }
71 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
72 {
73         Connector* conn = resman.reserve_connector(str);
75         if (!conn)
76                 EXIT("No connector '%s'", str.c_str());
78         if (!conn->connected())
79                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
81         output.connector = conn;
82         output.mode = output.connector->get_default_mode();
83 }
85 static void get_default_crtc(Card& card, OutputInfo& output)
86 {
87         Crtc* crtc = output.connector->get_current_crtc();
89         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
90                 s_used_crtcs.insert(crtc);
91                 output.crtc = crtc;
92                 return;
93         }
95         for (const auto& possible : output.connector->get_possible_crtcs()) {
96                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
97                         s_used_crtcs.insert(possible);
98                         output.crtc = possible;
99                         return;
100                 }
101         }
103         EXIT("Could not find available crtc");
106 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
108         // @12:1920x1200i@60
109         // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
111         const regex modename_re("(?:(@?)(\\d+):)?"      // @12:
112                                 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
113                                 "(?:@([\\d\\.]+))?");   // @60
115         const regex modeline_re("(?:(@?)(\\d+):)?"                      // @12:
116                                 "(\\d+),"                               // 33000000,
117                                 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-]),"   // 800/210/30/16/-,
118                                 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])"    // 480/22/13/10/-
119                                 "(?:,([i]+))?"                          // ,i
120                                 );
122         smatch sm;
123         if (regex_match(crtc_str, sm, modename_re)) {
124                 if (sm[2].matched) {
125                         bool use_id = sm[1].length() == 1;
126                         unsigned num = stoul(sm[2].str());
128                         if (use_id) {
129                                 Crtc* c = card.get_crtc(num);
130                                 if (!c)
131                                         EXIT("Bad crtc id '%u'", num);
133                                 output.crtc = c;
134                         } else {
135                                 auto crtcs = card.get_crtcs();
137                                 if (num >= crtcs.size())
138                                         EXIT("Bad crtc number '%u'", num);
140                                 output.crtc = crtcs[num];
141                         }
142                 } else {
143                         output.crtc = output.connector->get_current_crtc();
144                 }
146                 unsigned w = stoul(sm[3]);
147                 unsigned h = stoul(sm[4]);
148                 bool ilace = sm[5].matched ? true : false;
149                 float refresh = sm[6].matched ? stof(sm[6]) : 0;
151                 if (s_cvt) {
152                         output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
153                 } else if (s_use_dmt) {
154                         try {
155                                 output.mode = find_dmt(w, h, refresh, ilace);
156                         } catch (exception& e) {
157                                 EXIT("Mode not found from DMT tables\n");
158                         }
159                 } else if (s_use_cea) {
160                         try {
161                                 output.mode = find_cea(w, h, refresh, ilace);
162                         } catch (exception& e) {
163                                 EXIT("Mode not found from CEA tables\n");
164                         }
165                 } else {
166                         try {
167                                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
168                         } catch (exception& e) {
169                                 EXIT("Mode not found from the connector\n");
170                         }
171                 }
172         } else if (regex_match(crtc_str, sm, modeline_re)) {
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                                 Crtc* c = card.get_crtc(num);
179                                 if (!c)
180                                         EXIT("Bad crtc id '%u'", num);
182                                 output.crtc = c;
183                         } else {
184                                 auto crtcs = card.get_crtcs();
186                                 if (num >= crtcs.size())
187                                         EXIT("Bad crtc number '%u'", num);
189                                 output.crtc = crtcs[num];
190                         }
191                 } else {
192                         output.crtc = output.connector->get_current_crtc();
193                 }
195                 unsigned clock = stoul(sm[3]);
197                 unsigned hact = stoul(sm[4]);
198                 unsigned hfp = stoul(sm[5]);
199                 unsigned hsw = stoul(sm[6]);
200                 unsigned hbp = stoul(sm[7]);
201                 bool h_pos_sync = sm[8] == "+" ? true : false;
203                 unsigned vact = stoul(sm[9]);
204                 unsigned vfp = stoul(sm[10]);
205                 unsigned vsw = stoul(sm[11]);
206                 unsigned vbp = stoul(sm[12]);
207                 bool v_pos_sync = sm[13] == "+" ? true : false;
209                 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
210                 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
211                 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
213                 if (sm[14].matched) {
214                         for (int i = 0; i < sm[14].length(); ++i) {
215                                 char f = string(sm[14])[i];
217                                 switch (f) {
218                                 case 'i':
219                                         output.mode.set_interlace(true);
220                                         break;
221                                 default:
222                                         EXIT("Bad mode flag %c", f);
223                                 }
224                         }
225                 }
226         } else {
227                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
228         }
231 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
233         // 3:400,400-400x400
234         const regex plane_re("(?:(@?)(\\d+):)?"         // 3:
235                              "(?:(\\d+),(\\d+)-)?"      // 400,400-
236                              "(\\d+)x(\\d+)");          // 400x400
238         smatch sm;
239         if (!regex_match(plane_str, sm, plane_re))
240                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
242         if (sm[2].matched) {
243                 bool use_id = sm[1].length() == 1;
244                 unsigned num = stoul(sm[2].str());
246                 if (use_id) {
247                         Plane* p = card.get_plane(num);
248                         if (!p)
249                                 EXIT("Bad plane id '%u'", num);
251                         pinfo.plane = p;
252                 } else {
253                         auto planes = card.get_planes();
255                         if (num >= planes.size())
256                                 EXIT("Bad plane number '%u'", num);
258                         pinfo.plane = planes[num];
259                 }
260         } else {
261                 for (Plane* p : output.crtc->get_possible_planes()) {
262                         if (s_used_planes.find(p) != s_used_planes.end())
263                                 continue;
265                         if (p->plane_type() != PlaneType::Overlay)
266                                 continue;
268                         pinfo.plane = p;
269                 }
271                 if (!pinfo.plane)
272                         EXIT("Failed to find available plane");
273         }
275         s_used_planes.insert(pinfo.plane);
277         pinfo.w = stoul(sm[5]);
278         pinfo.h = stoul(sm[6]);
280         if (sm[3].matched)
281                 pinfo.x = stoul(sm[3]);
282         else
283                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
285         if (sm[4].matched)
286                 pinfo.y = stoul(sm[4]);
287         else
288                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
291 static vector<MappedFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
293         vector<MappedFramebuffer*> v;
295         for (unsigned i = 0; i < s_num_buffers; ++i)
296                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
298         return v;
301 static vector<MappedFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
303         unsigned w = def_w;
304         unsigned h = def_h;
305         PixelFormat format = PixelFormat::XRGB8888;
307         if (!fb_str.empty()) {
308                 // XXX the regexp is not quite correct
309                 // 400x400-NV12
310                 const regex fb_re("(?:(\\d+)x(\\d+))?"          // 400x400
311                                   "(?:-)?"                      // -
312                                   "(\\w\\w\\w\\w)?");           // NV12
314                 smatch sm;
315                 if (!regex_match(fb_str, sm, fb_re))
316                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
318                 if (sm[1].matched)
319                         w = stoul(sm[1]);
320                 if (sm[2].matched)
321                         h = stoul(sm[2]);
322                 if (sm[3].matched)
323                         format = FourCCToPixelFormat(sm[3]);
324         }
326         vector<MappedFramebuffer*> v;
328         for (unsigned i = 0; i < s_num_buffers; ++i)
329                 v.push_back(new DumbFramebuffer(card, w, h, format));
331         return v;
334 static void parse_view(const string& view_str, PlaneInfo& pinfo)
336         const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)");             // 400,400-400x400
338         smatch sm;
339         if (!regex_match(view_str, sm, view_re))
340                 EXIT("Failed to parse view option '%s'", view_str.c_str());
342         pinfo.view_x = stoul(sm[1]);
343         pinfo.view_y = stoul(sm[2]);
344         pinfo.view_w = stoul(sm[3]);
345         pinfo.view_h = stoul(sm[4]);
348 static const char* usage_str =
349                 "Usage: kmstest [OPTION]...\n\n"
350                 "Show a test pattern on a display or plane\n\n"
351                 "Options:\n"
352                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
353                 "  -c, --connector=CONN      CONN is <connector>\n"
354                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
355                 "                            or\n"
356                 "                            [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
357                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
358                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
359                 "  -v, --view=VIEW           VIEW is <x>,<y>-<w>x<h>\n"
360                 "      --dmt                 Search for the given mode from DMT tables\n"
361                 "      --cea                 Search for the given mode from CEA tables\n"
362                 "      --cvt=CVT             Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
363                 "      --flip[=max]          Do page flipping for each output with an optional maximum flips count\n"
364                 "      --sync                Synchronize page flipping\n"
365                 "\n"
366                 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n"
367                 "<connector> can also be given by name.\n"
368                 "\n"
369                 "Options can be given multiple times to set up multiple displays or planes.\n"
370                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
371                 "an earlier option.\n"
372                 "If you omit parameters, kmstest tries to guess what you mean\n"
373                 "\n"
374                 "Examples:\n"
375                 "\n"
376                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
377                 "    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
378                 "XR24 framebuffer on first connected connector in the default mode:\n"
379                 "    kmstest -f XR24\n\n"
380                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
381                 "    kmstest -p 400x400 -f XR24\n\n"
382                 "Test pattern on the second connector with default mode:\n"
383                 "    kmstest -c 1\n"
384                 "\n"
385                 "Environmental variables:\n"
386                 "    KMSXX_DISABLE_UNIVERSAL_PLANES    Don't enable universal planes even if available\n"
387                 "    KMSXX_DISABLE_ATOMIC              Don't enable atomic modesetting even if available\n"
388                 ;
390 static void usage()
392         puts(usage_str);
395 enum class ObjectType
397         Connector,
398         Crtc,
399         Plane,
400         Framebuffer,
401         View,
402 };
404 struct Arg
406         ObjectType type;
407         string arg;
408 };
410 static string s_device_path = "/dev/dri/card0";
412 static vector<Arg> parse_cmdline(int argc, char **argv)
414         vector<Arg> args;
416         OptionSet optionset = {
417                 Option("|device=",
418                 [&](string s)
419                 {
420                         s_device_path = s;
421                 }),
422                 Option("c|connector=",
423                 [&](string s)
424                 {
425                         args.push_back(Arg { ObjectType::Connector, s });
426                 }),
427                 Option("r|crtc=", [&](string s)
428                 {
429                         args.push_back(Arg { ObjectType::Crtc, s });
430                 }),
431                 Option("p|plane=", [&](string s)
432                 {
433                         args.push_back(Arg { ObjectType::Plane, s });
434                 }),
435                 Option("f|fb=", [&](string s)
436                 {
437                         args.push_back(Arg { ObjectType::Framebuffer, s });
438                 }),
439                 Option("v|view=", [&](string s)
440                 {
441                         args.push_back(Arg { ObjectType::View, s });
442                 }),
443                 Option("|dmt", []()
444                 {
445                         s_use_dmt = true;
446                 }),
447                 Option("|cea", []()
448                 {
449                         s_use_cea = true;
450                 }),
451                 Option("|flip?", [&](string s)
452                 {
453                         s_flip_mode = true;
454                         s_num_buffers = 2;
455                         if (!s.empty())
456                                 s_max_flips = stoi(s);
457                 }),
458                 Option("|sync", []()
459                 {
460                         s_flip_sync = true;
461                 }),
462                 Option("|cvt=", [&](string s)
463                 {
464                         if (s == "v1")
465                                 s_cvt = true;
466                         else if (s == "v2")
467                                 s_cvt = s_cvt_v2 = true;
468                         else if (s == "v2o")
469                                 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
470                         else {
471                                 usage();
472                                 exit(-1);
473                         }
474                 }),
475                 Option("h|help", [&]()
476                 {
477                         usage();
478                         exit(-1);
479                 }),
480         };
482         optionset.parse(argc, argv);
484         if (optionset.params().size() > 0) {
485                 usage();
486                 exit(-1);
487         }
489         return args;
492 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
494         vector<OutputInfo> outputs;
496         if (output_args.size() == 0) {
497                 // no output args, show a pattern on all screens
498                 for (Connector* conn : card.get_connectors()) {
499                         if (!conn->connected())
500                                 continue;
502                         OutputInfo output = { };
503                         output.connector = resman.reserve_connector(conn);
504                         EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str());
505                         output.crtc = resman.reserve_crtc(conn);
506                         EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str());
507                         output.mode = output.connector->get_default_mode();
509                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
511                         outputs.push_back(output);
512                 }
514                 return outputs;
515         }
517         OutputInfo* current_output = 0;
518         PlaneInfo* current_plane = 0;
520         for (auto& arg : output_args) {
521                 switch (arg.type) {
522                 case ObjectType::Connector:
523                 {
524                         outputs.push_back(OutputInfo { });
525                         current_output = &outputs.back();
527                         get_connector(resman, *current_output, arg.arg);
528                         current_plane = 0;
530                         break;
531                 }
533                 case ObjectType::Crtc:
534                 {
535                         if (!current_output) {
536                                 outputs.push_back(OutputInfo { });
537                                 current_output = &outputs.back();
538                         }
540                         if (!current_output->connector)
541                                 get_connector(resman, *current_output);
543                         parse_crtc(card, arg.arg, *current_output);
545                         current_output->user_set_crtc = true;
547                         current_plane = 0;
549                         break;
550                 }
552                 case ObjectType::Plane:
553                 {
554                         if (!current_output) {
555                                 outputs.push_back(OutputInfo { });
556                                 current_output = &outputs.back();
557                         }
559                         if (!current_output->connector)
560                                 get_connector(resman, *current_output);
562                         if (!current_output->crtc)
563                                 get_default_crtc(card, *current_output);
565                         current_output->planes.push_back(PlaneInfo { });
566                         current_plane = &current_output->planes.back();
568                         parse_plane(card, arg.arg, *current_output, *current_plane);
570                         break;
571                 }
573                 case ObjectType::Framebuffer:
574                 {
575                         if (!current_output) {
576                                 outputs.push_back(OutputInfo { });
577                                 current_output = &outputs.back();
578                         }
580                         if (!current_output->connector)
581                                 get_connector(resman, *current_output);
583                         if (!current_output->crtc)
584                                 get_default_crtc(card, *current_output);
586                         int def_w, def_h;
588                         if (current_plane) {
589                                 def_w = current_plane->w;
590                                 def_h = current_plane->h;
591                         } else {
592                                 def_w = current_output->mode.hdisplay;
593                                 def_h = current_output->mode.vdisplay;
594                         }
596                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
598                         if (current_plane)
599                                 current_plane->fbs = fbs;
600                         else
601                                 current_output->fbs = fbs;
603                         break;
604                 }
606                 case ObjectType::View:
607                 {
608                         if (!current_plane || current_plane->fbs.empty())
609                                 EXIT("'view' parameter requires a plane and a fb");
611                         parse_view(arg.arg, *current_plane);
612                         break;
613                 }
614                 }
615         }
617         // create default framebuffers if needed
618         for (OutputInfo& o : outputs) {
619                 if (!o.crtc) {
620                         get_default_crtc(card, o);
621                         o.user_set_crtc = true;
622                 }
624                 if (o.fbs.empty() && o.user_set_crtc)
625                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
627                 for (PlaneInfo &p : o.planes) {
628                         if (p.fbs.empty())
629                                 p.fbs = get_default_fb(card, p.w, p.h);
630                 }
631         }
633         return outputs;
636 static std::string videomode_to_string(const Videomode& m)
638         string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
639         string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
641         return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
642                        m.name.c_str(),
643                        m.clock / 1000.0,
644                        h.c_str(), v.c_str(),
645                        m.vrefresh, m.calculated_vrefresh(),
646                        m.flags,
647                        m.type);
650 static void print_outputs(const vector<OutputInfo>& outputs)
652         for (unsigned i = 0; i < outputs.size(); ++i) {
653                 const OutputInfo& o = outputs[i];
655                 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
656                        o.connector->fullname().c_str());
657                 printf("  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
658                 if (o.primary_plane)
659                         printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
660                 printf(": %s\n", videomode_to_string(o.mode).c_str());
661                 if (!o.fbs.empty()) {
662                         auto fb = o.fbs[0];
663                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
664                                PixelFormatToFourCC(fb->format()).c_str());
665                 }
667                 for (unsigned j = 0; j < o.planes.size(); ++j) {
668                         const PlaneInfo& p = o.planes[j];
669                         auto fb = p.fbs[0];
670                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
671                                p.x, p.y, p.w, p.h);
672                         printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
673                                PixelFormatToFourCC(fb->format()).c_str());
674                 }
675         }
678 static void draw_test_patterns(const vector<OutputInfo>& outputs)
680         for (const OutputInfo& o : outputs) {
681                 for (auto fb : o.fbs)
682                         draw_test_pattern(*fb);
684                 for (const PlaneInfo& p : o.planes)
685                         for (auto fb : p.fbs)
686                                 draw_test_pattern(*fb);
687         }
690 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
692         // Disable unused crtcs
693         for (Crtc* crtc : card.get_crtcs()) {
694                 if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
695                         continue;
697                 crtc->disable_mode();
698         }
700         for (const OutputInfo& o : outputs) {
701                 auto conn = o.connector;
702                 auto crtc = o.crtc;
704                 if (!o.fbs.empty()) {
705                         auto fb = o.fbs[0];
706                         int r = crtc->set_mode(conn, *fb, o.mode);
707                         if (r)
708                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
709                                        crtc->id(), strerror(-r));
710                 }
712                 for (const PlaneInfo& p : o.planes) {
713                         auto fb = p.fbs[0];
714                         int r = crtc->set_plane(p.plane, *fb,
715                                                 p.x, p.y, p.w, p.h,
716                                                 0, 0, fb->width(), fb->height());
717                         if (r)
718                                 printf("crtc->set_plane() failed for plane %u: %s\n",
719                                        p.plane->id(), strerror(-r));
720                 }
721         }
724 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
726         int r;
728         // XXX DRM framework doesn't allow moving an active plane from one crtc to another.
729         // See drm_atomic.c::plane_switching_crtc().
730         // For the time being, disable all crtcs and planes here.
732         AtomicReq disable_req(card);
734         // Disable unused crtcs
735         for (Crtc* crtc : card.get_crtcs()) {
736                 //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
737                 //      continue;
739                 disable_req.add(crtc, {
740                                 { "ACTIVE", 0 },
741                         });
742         }
744         // Disable unused planes
745         for (Plane* plane : card.get_planes()) {
746                 //if (find_if(outputs.begin(), outputs.end(), [plane](const OutputInfo& o) { return o.primary_plane == plane; }) != outputs.end())
747                 //      continue;
749                 disable_req.add(plane, {
750                                 { "FB_ID", 0 },
751                                 { "CRTC_ID", 0 },
752                         });
753         }
755         r = disable_req.commit_sync(true);
756         if (r)
757                 EXIT("Atomic commit failed when disabling: %d\n", r);
760         // Keep blobs here so that we keep ref to them until we have committed the req
761         vector<unique_ptr<Blob>> blobs;
763         AtomicReq req(card);
765         for (const OutputInfo& o : outputs) {
766                 auto conn = o.connector;
767                 auto crtc = o.crtc;
769                 blobs.emplace_back(o.mode.to_blob(card));
770                 Blob* mode_blob = blobs.back().get();
772                 req.add(conn, {
773                                 { "CRTC_ID", crtc->id() },
774                         });
776                 req.add(crtc, {
777                                 { "ACTIVE", 1 },
778                                 { "MODE_ID", mode_blob->id() },
779                         });
781                 if (!o.fbs.empty()) {
782                         auto fb = o.fbs[0];
784                         req.add(o.primary_plane, {
785                                         { "FB_ID", fb->id() },
786                                         { "CRTC_ID", crtc->id() },
787                                         { "SRC_X", 0 << 16 },
788                                         { "SRC_Y", 0 << 16 },
789                                         { "SRC_W", fb->width() << 16 },
790                                         { "SRC_H", fb->height() << 16 },
791                                         { "CRTC_X", 0 },
792                                         { "CRTC_Y", 0 },
793                                         { "CRTC_W", fb->width() },
794                                         { "CRTC_H", fb->height() },
795                                 });
796                 }
798                 for (const PlaneInfo& p : o.planes) {
799                         auto fb = p.fbs[0];
801                         req.add(p.plane, {
802                                         { "FB_ID", fb->id() },
803                                         { "CRTC_ID", crtc->id() },
804                                         { "SRC_X", (p.view_x ?: 0) << 16 },
805                                         { "SRC_Y", (p.view_y ?: 0) << 16 },
806                                         { "SRC_W", (p.view_w ?: fb->width()) << 16 },
807                                         { "SRC_H", (p.view_h ?: fb->height()) << 16 },
808                                         { "CRTC_X", p.x },
809                                         { "CRTC_Y", p.y },
810                                         { "CRTC_W", p.w },
811                                         { "CRTC_H", p.h },
812                                 });
813                 }
814         }
816         r = req.test(true);
817         if (r)
818                 EXIT("Atomic test failed: %d\n", r);
820         r = req.commit_sync(true);
821         if (r)
822                 EXIT("Atomic commit failed: %d\n", r);
825 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
827         if (card.has_atomic())
828                 set_crtcs_n_planes_atomic(card, outputs);
829         else
830                 set_crtcs_n_planes_legacy(card, outputs);
833 static bool max_flips_reached;
835 class FlipState : private PageFlipHandlerBase
837 public:
838         FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
839                 : m_card(card), m_name(name), m_outputs(outputs)
840         {
841         }
843         void start_flipping()
844         {
845                 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
846                 m_slowest_frame = std::chrono::duration<float>::min();
847                 m_frame_num = 0;
848                 queue_next();
849         }
851 private:
852         void handle_page_flip(uint32_t frame, double time)
853         {
854                 /*
855                  * We get flip event for each crtc in this flipstate. We can commit the next frames
856                  * only after we've gotten the flip event for all crtcs
857                  */
858                 if (++m_flip_count < m_outputs.size())
859                         return;
861                 m_frame_num++;
862                 if (s_max_flips && m_frame_num >= s_max_flips)
863                         max_flips_reached = true;
865                 auto now = std::chrono::steady_clock::now();
867                 std::chrono::duration<float> diff = now - m_prev_frame;
868                 if (diff > m_slowest_frame)
869                         m_slowest_frame = diff;
871                 if (m_frame_num  % 100 == 0) {
872                         std::chrono::duration<float> fsec = now - m_prev_print;
873                         printf("Connector %s: fps %f, slowest %.2f ms\n",
874                                m_name.c_str(),
875                                100.0 / fsec.count(),
876                                m_slowest_frame.count() * 1000);
877                         m_prev_print = now;
878                         m_slowest_frame = std::chrono::duration<float>::min();
879                 }
881                 m_prev_frame = now;
883                 queue_next();
884         }
886         static unsigned get_bar_pos(MappedFramebuffer* fb, unsigned frame_num)
887         {
888                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
889         }
891         static void draw_bar(MappedFramebuffer* fb, unsigned frame_num)
892         {
893                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
894                 int new_xpos = get_bar_pos(fb, frame_num);
896                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
897                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
898         }
900         static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
901         {
902                 unsigned cur = frame_num % s_num_buffers;
904                 if (!o.fbs.empty()) {
905                         auto fb = o.fbs[cur];
907                         draw_bar(fb, frame_num);
909                         req.add(o.primary_plane, {
910                                         { "FB_ID", fb->id() },
911                                 });
912                 }
914                 for (const PlaneInfo& p : o.planes) {
915                         auto fb = p.fbs[cur];
917                         draw_bar(fb, frame_num);
919                         req.add(p.plane, {
920                                         { "FB_ID", fb->id() },
921                                 });
922                 }
923         }
925         void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
926         {
927                 unsigned cur = frame_num % s_num_buffers;
929                 if (!o.fbs.empty()) {
930                         auto fb = o.fbs[cur];
932                         draw_bar(fb, frame_num);
934                         int r = o.crtc->page_flip(*fb, this);
935                         ASSERT(r == 0);
936                 }
938                 for (const PlaneInfo& p : o.planes) {
939                         auto fb = p.fbs[cur];
941                         draw_bar(fb, frame_num);
943                         int r = o.crtc->set_plane(p.plane, *fb,
944                                                   p.x, p.y, p.w, p.h,
945                                                   0, 0, fb->width(), fb->height());
946                         ASSERT(r == 0);
947                 }
948         }
950         void queue_next()
951         {
952                 m_flip_count = 0;
954                 if (m_card.has_atomic()) {
955                         AtomicReq req(m_card);
957                         for (auto o : m_outputs)
958                                 do_flip_output(req, m_frame_num, *o);
960                         int r = req.commit(this);
961                         if (r)
962                                 EXIT("Flip commit failed: %d\n", r);
963                 } else {
964                         ASSERT(m_outputs.size() == 1);
965                         do_flip_output_legacy(m_frame_num, *m_outputs[0]);
966                 }
967         }
969         Card& m_card;
970         string m_name;
971         vector<const OutputInfo*> m_outputs;
972         unsigned m_frame_num;
973         unsigned m_flip_count;
975         chrono::steady_clock::time_point m_prev_print;
976         chrono::steady_clock::time_point m_prev_frame;
977         chrono::duration<float> m_slowest_frame;
979         static const unsigned bar_width = 20;
980         static const unsigned bar_speed = 8;
981 };
983 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
985         fd_set fds;
987         FD_ZERO(&fds);
989         int fd = card.fd();
991         vector<unique_ptr<FlipState>> flipstates;
993         if (!s_flip_sync) {
994                 for (const OutputInfo& o : outputs) {
995                         auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
996                         flipstates.push_back(move(fs));
997                 }
998         } else {
999                 vector<const OutputInfo*> ois;
1001                 string name;
1002                 for (const OutputInfo& o : outputs) {
1003                         name += to_string(o.connector->idx()) + ",";
1004                         ois.push_back(&o);
1005                 }
1007                 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1008                 flipstates.push_back(move(fs));
1009         }
1011         for (unique_ptr<FlipState>& fs : flipstates)
1012                 fs->start_flipping();
1014         while (!max_flips_reached) {
1015                 int r;
1017                 FD_SET(0, &fds);
1018                 FD_SET(fd, &fds);
1020                 r = select(fd + 1, &fds, NULL, NULL, NULL);
1021                 if (r < 0) {
1022                         fprintf(stderr, "select() failed with %d: %m\n", errno);
1023                         break;
1024                 } else if (FD_ISSET(0, &fds)) {
1025                         fprintf(stderr, "Exit due to user-input\n");
1026                         break;
1027                 } else if (FD_ISSET(fd, &fds)) {
1028                         card.call_page_flip_handlers();
1029                 }
1030         }
1033 int main(int argc, char **argv)
1035         vector<Arg> output_args = parse_cmdline(argc, argv);
1037         Card card(s_device_path);
1039         if (!card.has_atomic() && s_flip_sync)
1040                 EXIT("Synchronized flipping requires atomic modesetting");
1042         ResourceManager resman(card);
1044         vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1046         if (card.has_atomic()) {
1047                 for (OutputInfo& o : outputs) {
1048                         if (o.fbs.empty())
1049                                 continue;
1051                         o.primary_plane = resman.reserve_primary_plane(o.crtc);
1053                         if (!o.primary_plane)
1054                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
1055                 }
1056         }
1058         if (!s_flip_mode)
1059                 draw_test_patterns(outputs);
1061         print_outputs(outputs);
1063         set_crtcs_n_planes(card, outputs);
1065         printf("press enter to exit\n");
1067         if (s_flip_mode)
1068                 main_flip(card, outputs);
1069         else
1070                 getchar();