6aa658345e6cea14855350cf33fb20e2d870842f
[android/external-libkmsxx.git] / utils / testpat.cpp
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
7 #include <kms++.h>
8 #include <modedb.h>
10 #include <kms++util.h>
11 #include <opts.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;
45 static set<Crtc*> s_used_crtcs;
46 static set<Plane*> s_used_planes;
48 __attribute__ ((unused))
49 static void print_regex_match(smatch sm)
50 {
51         for (unsigned i = 0; i < sm.size(); ++i) {
52                 string str = sm[i].str();
53                 printf("%u: %s\n", i, str.c_str());
54         }
55 }
57 static void get_default_connector(Card& card, OutputInfo& output)
58 {
59         output.connector = card.get_first_connected_connector();
60         output.mode = output.connector->get_default_mode();
61 }
63 static void parse_connector(Card& card, const string& str, OutputInfo& output)
64 {
65         Connector* conn = nullptr;
67         auto connectors = card.get_connectors();
69         if (str[0] == '@') {
70                 char* endptr;
71                 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
72                 if (*endptr == 0) {
73                         if (idx >= connectors.size())
74                                 EXIT("Bad connector number '%u'", idx);
76                         conn = connectors[idx];
77                 }
78         } else {
79                 char* endptr;
80                 unsigned id = strtoul(str.c_str(), &endptr, 10);
81                 if (*endptr == 0) {
82                         Connector* c = card.get_connector(id);
83                         if (!c)
84                                 EXIT("Bad connector id '%u'", id);
86                         conn = c;
87                 }
88         }
90         if (!conn) {
91                 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
92                 if (iter != connectors.end())
93                         conn = *iter;
94         }
96         if (!conn)
97                 EXIT("No connector '%s'", str.c_str());
99         if (!conn->connected())
100                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
102         output.connector = conn;
103         output.mode = output.connector->get_default_mode();
106 static void get_default_crtc(Card& card, OutputInfo& output)
108         Crtc* crtc = output.connector->get_current_crtc();
110         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
111                 s_used_crtcs.insert(crtc);
112                 output.crtc = crtc;
113                 return;
114         }
116         for (const auto& possible : output.connector->get_possible_crtcs()) {
117                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
118                         s_used_crtcs.insert(possible);
119                         output.crtc = possible;
120                         return;
121                 }
122         }
124         EXIT("Could not find available crtc");
127 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
129         // @12:1920x1200@60
130         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
132         smatch sm;
133         if (!regex_match(crtc_str, sm, mode_re))
134                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
136         if (sm[2].matched) {
137                 bool use_idx = sm[1].length() == 1;
138                 unsigned num = stoul(sm[2].str());
140                 if (use_idx) {
141                         auto crtcs = card.get_crtcs();
143                         if (num >= crtcs.size())
144                                 EXIT("Bad crtc number '%u'", num);
146                         output.crtc = crtcs[num];
147                 } else {
148                         Crtc* c = card.get_crtc(num);
149                         if (!c)
150                                 EXIT("Bad crtc id '%u'", num);
152                         output.crtc = c;
153                 }
154         } else {
155                 output.crtc = output.connector->get_current_crtc();
156         }
158         unsigned w = stoul(sm[3]);
159         unsigned h = stoul(sm[4]);
160         bool ilace = sm[5].matched ? true : false;
161         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
163         bool found_mode = false;
165         try {
166                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
167                 found_mode = true;
168         } catch (exception& e) { }
170         if (!found_mode && s_use_dmt) {
171                 try {
172                         output.mode = find_dmt(w, h, refresh, ilace);
173                         found_mode = true;
174                         printf("Found mode from DMT\n");
175                 } catch (exception& e) { }
176         }
178         if (!found_mode && s_use_cea) {
179                 try {
180                         output.mode = find_cea(w, h, refresh, ilace);
181                         found_mode = true;
182                         printf("Found mode from CEA\n");
183                 } catch (exception& e) { }
184         }
186         if (!found_mode)
187                 throw invalid_argument("Mode not found");
190 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
192         // 3:400,400-400x400
193         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
195         smatch sm;
196         if (!regex_match(plane_str, sm, plane_re))
197                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
199         if (sm[2].matched) {
200                 bool use_idx = sm[1].length() == 1;
201                 unsigned num = stoul(sm[2].str());
203                 if (use_idx) {
204                         auto planes = card.get_planes();
206                         if (num >= planes.size())
207                                 EXIT("Bad plane number '%u'", num);
209                         pinfo.plane = planes[num];
210                 } else {
211                         Plane* p = card.get_plane(num);
212                         if (!p)
213                                 EXIT("Bad plane id '%u'", num);
215                         pinfo.plane = p;
216                 }
217         } else {
218                 for (Plane* p : output.crtc->get_possible_planes()) {
219                         if (s_used_planes.find(p) != s_used_planes.end())
220                                 continue;
222                         if (p->plane_type() != PlaneType::Overlay)
223                                 continue;
225                         pinfo.plane = p;
226                 }
228                 if (!pinfo.plane)
229                         EXIT("Failed to find available plane");
230         }
232         s_used_planes.insert(pinfo.plane);
234         pinfo.w = stoul(sm[5]);
235         pinfo.h = stoul(sm[6]);
237         if (sm[3].matched)
238                 pinfo.x = stoul(sm[3]);
239         else
240                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
242         if (sm[4].matched)
243                 pinfo.y = stoul(sm[4]);
244         else
245                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
248 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
250         vector<DumbFramebuffer*> v;
252         for (unsigned i = 0; i < s_num_buffers; ++i)
253                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
255         return v;
258 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
260         unsigned w = def_w;
261         unsigned h = def_h;
262         PixelFormat format = PixelFormat::XRGB8888;
264         if (!fb_str.empty()) {
265                 // XXX the regexp is not quite correct
266                 // 400x400-NV12
267                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
269                 smatch sm;
270                 if (!regex_match(fb_str, sm, fb_re))
271                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
273                 if (sm[1].matched)
274                         w = stoul(sm[1]);
275                 if (sm[2].matched)
276                         h = stoul(sm[2]);
277                 if (sm[3].matched)
278                         format = FourCCToPixelFormat(sm[3]);
279         }
281         vector<DumbFramebuffer*> v;
283         for (unsigned i = 0; i < s_num_buffers; ++i)
284                 v.push_back(new DumbFramebuffer(card, w, h, format));
286         return v;
289 static const char* usage_str =
290                 "Usage: testpat [OPTION]...\n\n"
291                 "Show a test pattern on a display or plane\n\n"
292                 "Options:\n"
293                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
294                 "  -c, --connector=CONN      CONN is <connector>\n"
295                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
296                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
297                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
298                 "      --dmt                 Search for the given mode from DMT tables\n"
299                 "      --cea                 Search for the given mode from CEA tables\n"
300                 "\n"
301                 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
302                 "<connector> can also be given by name.\n"
303                 "\n"
304                 "Options can be given multiple times to set up multiple displays or planes.\n"
305                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
306                 "an earlier option.\n"
307                 "If you omit parameters, testpat tries to guess what you mean\n"
308                 "\n"
309                 "Examples:\n"
310                 "\n"
311                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
312                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
313                 "XR24 framebuffer on first connected connector in the default mode:\n"
314                 "    testpat -f XR24\n\n"
315                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
316                 "    testpat -p 400x400 -f XR24\n\n"
317                 "Test pattern on the second connector with default mode:\n"
318                 "    testpat -c @1\n"
319                 ;
321 static void usage()
323         puts(usage_str);
326 enum class ObjectType
328         Connector,
329         Crtc,
330         Plane,
331         Framebuffer,
332 };
334 struct Arg
336         ObjectType type;
337         string arg;
338 };
340 static string s_device_path = "/dev/dri/card0";
342 static vector<Arg> parse_cmdline(int argc, char **argv)
344         vector<Arg> args;
346         OptionSet optionset = {
347                 Option("|device=",
348                 [&](string s)
349                 {
350                         s_device_path = s;
351                 }),
352                 Option("c|connector=",
353                 [&](string s)
354                 {
355                         args.push_back(Arg { ObjectType::Connector, s });
356                 }),
357                 Option("r|crtc=", [&](string s)
358                 {
359                         args.push_back(Arg { ObjectType::Crtc, s });
360                 }),
361                 Option("p|plane=", [&](string s)
362                 {
363                         args.push_back(Arg { ObjectType::Plane, s });
364                 }),
365                 Option("f|fb=", [&](string s)
366                 {
367                         args.push_back(Arg { ObjectType::Framebuffer, s });
368                 }),
369                 Option("|dmt", []()
370                 {
371                         s_use_dmt = true;
372                 }),
373                 Option("|cea", []()
374                 {
375                         s_use_cea = true;
376                 }),
377                 Option("h|help", [&]()
378                 {
379                         usage();
380                         exit(-1);
381                 }),
382         };
384         optionset.parse(argc, argv);
386         if (optionset.params().size() > 0) {
387                 usage();
388                 exit(-1);
389         }
391         return args;
394 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
396         vector<OutputInfo> outputs;
398         if (output_args.size() == 0) {
399                 // no output args, show a pattern on all screens
400                 for (auto& pipe : card.get_connected_pipelines()) {
401                         OutputInfo output = { };
402                         output.connector = pipe.connector;
403                         output.crtc = pipe.crtc;
404                         output.mode = output.connector->get_default_mode();
406                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
408                         outputs.push_back(output);
409                 }
411                 return outputs;
412         }
414         OutputInfo* current_output = 0;
415         PlaneInfo* current_plane = 0;
417         for (auto& arg : output_args) {
418                 switch (arg.type) {
419                 case ObjectType::Connector:
420                 {
421                         outputs.push_back(OutputInfo { });
422                         current_output = &outputs.back();
424                         parse_connector(card, arg.arg, *current_output);
425                         current_plane = 0;
427                         break;
428                 }
430                 case ObjectType::Crtc:
431                 {
432                         if (!current_output) {
433                                 outputs.push_back(OutputInfo { });
434                                 current_output = &outputs.back();
435                         }
437                         if (!current_output->connector)
438                                 get_default_connector(card, *current_output);
440                         parse_crtc(card, arg.arg, *current_output);
442                         current_output->user_set_crtc = true;
444                         current_plane = 0;
446                         break;
447                 }
449                 case ObjectType::Plane:
450                 {
451                         if (!current_output) {
452                                 outputs.push_back(OutputInfo { });
453                                 current_output = &outputs.back();
454                         }
456                         if (!current_output->connector)
457                                 get_default_connector(card, *current_output);
459                         if (!current_output->crtc)
460                                 get_default_crtc(card, *current_output);
462                         current_output->planes.push_back(PlaneInfo { });
463                         current_plane = &current_output->planes.back();
465                         parse_plane(card, arg.arg, *current_output, *current_plane);
467                         break;
468                 }
470                 case ObjectType::Framebuffer:
471                 {
472                         if (!current_output) {
473                                 outputs.push_back(OutputInfo { });
474                                 current_output = &outputs.back();
475                         }
477                         if (!current_output->connector)
478                                 get_default_connector(card, *current_output);
480                         if (!current_output->crtc)
481                                 get_default_crtc(card, *current_output);
483                         int def_w, def_h;
485                         if (current_plane) {
486                                 def_w = current_plane->w;
487                                 def_h = current_plane->h;
488                         } else {
489                                 def_w = current_output->mode.hdisplay;
490                                 def_h = current_output->mode.vdisplay;
491                         }
493                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
495                         if (current_plane)
496                                 current_plane->fbs = fbs;
497                         else
498                                 current_output->fbs = fbs;
500                         break;
501                 }
502                 }
503         }
505         // create default framebuffers if needed
506         for (OutputInfo& o : outputs) {
507                 if (!o.crtc) {
508                         get_default_crtc(card, *current_output);
509                         o.user_set_crtc = true;
510                 }
512                 if (o.fbs.empty() && o.user_set_crtc)
513                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
515                 for (PlaneInfo &p : o.planes) {
516                         if (p.fbs.empty())
517                                 p.fbs = get_default_fb(card, p.w, p.h);
518                 }
519         }
521         return outputs;
524 static std::string videomode_to_string(const Videomode& mode)
526         unsigned hfp = mode.hsync_start - mode.hdisplay;
527         unsigned hsw = mode.hsync_end - mode.hsync_start;
528         unsigned hbp = mode.htotal - mode.hsync_end;
530         unsigned vfp = mode.vsync_start - mode.vdisplay;
531         unsigned vsw = mode.vsync_end - mode.vsync_start;
532         unsigned vbp = mode.vtotal - mode.vsync_end;
534         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
535         if (mode.flags & (1<<4)) // XXX interlace
536                 hz *= 2;
538         char buf[256];
540         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
541                 mode.clock / 1000.0,
542                 mode.hdisplay, hfp, hsw, hbp,
543                 mode.vdisplay, vfp, vsw, vbp,
544                 mode.vrefresh, hz);
546         return std::string(buf);
549 static void print_outputs(const vector<OutputInfo>& outputs)
551         for (unsigned i = 0; i < outputs.size(); ++i) {
552                 const OutputInfo& o = outputs[i];
554                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
555                        o.connector->fullname().c_str());
556                 printf("  Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
557                 if (o.primary_plane)
558                         printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
559                 printf(": %ux%u-%u (%s)\n",
560                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
561                        videomode_to_string(o.mode).c_str());
562                 if (!o.fbs.empty()) {
563                         auto fb = o.fbs[0];
564                         printf("    Fb %ux%u-%s\n", fb->width(), fb->height(),
565                                PixelFormatToFourCC(fb->format()).c_str());
566                 }
568                 for (unsigned j = 0; j < o.planes.size(); ++j) {
569                         const PlaneInfo& p = o.planes[j];
570                         auto fb = p.fbs[0];
571                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
572                                p.x, p.y, p.w, p.h);
573                         printf("    Fb %ux%u-%s\n", fb->width(), fb->height(),
574                                PixelFormatToFourCC(fb->format()).c_str());
575                 }
576         }
579 static void draw_test_patterns(const vector<OutputInfo>& outputs)
581         for (const OutputInfo& o : outputs) {
582                 for (auto fb : o.fbs)
583                         draw_test_pattern(*fb);
585                 for (const PlaneInfo& p : o.planes)
586                         for (auto fb : p.fbs)
587                                 draw_test_pattern(*fb);
588         }
591 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
593         for (const OutputInfo& o : outputs) {
594                 auto conn = o.connector;
595                 auto crtc = o.crtc;
597                 if (!o.fbs.empty()) {
598                         auto fb = o.fbs[0];
599                         int r = crtc->set_mode(conn, *fb, o.mode);
600                         if (r)
601                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
602                                        crtc->id(), strerror(-r));
603                 }
605                 for (const PlaneInfo& p : o.planes) {
606                         auto fb = p.fbs[0];
607                         int r = crtc->set_plane(p.plane, *fb,
608                                                 p.x, p.y, p.w, p.h,
609                                                 0, 0, fb->width(), fb->height());
610                         if (r)
611                                 printf("crtc->set_plane() failed for plane %u: %s\n",
612                                        p.plane->id(), strerror(-r));
613                 }
614         }
617 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
619         // Keep blobs here so that we keep ref to them until we have committed the req
620         vector<unique_ptr<Blob>> blobs;
622         AtomicReq req(card);
624         for (const OutputInfo& o : outputs) {
625                 auto conn = o.connector;
626                 auto crtc = o.crtc;
628                 if (!o.fbs.empty()) {
629                         auto fb = o.fbs[0];
631                         blobs.emplace_back(o.mode.to_blob(card));
632                         Blob* mode_blob = blobs.back().get();
634                         req.add(conn, {
635                                         { "CRTC_ID", crtc->id() },
636                                 });
638                         req.add(crtc, {
639                                         { "ACTIVE", 1 },
640                                         { "MODE_ID", mode_blob->id() },
641                                 });
643                         req.add(o.primary_plane, {
644                                         { "FB_ID", fb->id() },
645                                         { "CRTC_ID", crtc->id() },
646                                         { "SRC_X", 0 << 16 },
647                                         { "SRC_Y", 0 << 16 },
648                                         { "SRC_W", fb->width() << 16 },
649                                         { "SRC_H", fb->height() << 16 },
650                                         { "CRTC_X", 0 },
651                                         { "CRTC_Y", 0 },
652                                         { "CRTC_W", fb->width() },
653                                         { "CRTC_H", fb->height() },
654                                 });
655                 }
657                 for (const PlaneInfo& p : o.planes) {
658                         auto fb = p.fbs[0];
660                         req.add(p.plane, {
661                                         { "FB_ID", fb->id() },
662                                         { "CRTC_ID", crtc->id() },
663                                         { "SRC_X", 0 << 16 },
664                                         { "SRC_Y", 0 << 16 },
665                                         { "SRC_W", fb->width() << 16 },
666                                         { "SRC_H", fb->height() << 16 },
667                                         { "CRTC_X", p.x },
668                                         { "CRTC_Y", p.y },
669                                         { "CRTC_W", p.w },
670                                         { "CRTC_H", p.h },
671                                 });
672                 }
673         }
675         int r;
677         r = req.test(true);
678         if (r)
679                 EXIT("Atomic test failed: %d\n", r);
681         r = req.commit_sync(true);
682         if (r)
683                 EXIT("Atomic commit failed: %d\n", r);
686 int main(int argc, char **argv)
688         vector<Arg> output_args = parse_cmdline(argc, argv);
690         Card card(s_device_path);
692         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
694         if (card.has_atomic()) {
695                 for (OutputInfo& o : outputs) {
696                         o.primary_plane = o.crtc->get_primary_plane();
698                         if (!o.fbs.empty() && !o.primary_plane)
699                                 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
700                 }
701         }
703         draw_test_patterns(outputs);
705         print_outputs(outputs);
707         if (card.has_atomic())
708                 set_crtcs_n_planes(card, outputs);
709         else
710                 set_crtcs_n_planes_legacy(card, outputs);
712         printf("press enter to exit\n");
714         getchar();