testpat: support multiple buffers
[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         Videomode mode;
34         bool user_set_crtc;
35         vector<DumbFramebuffer*> fbs;
37         vector<PlaneInfo> planes;
38 };
40 static bool s_use_dmt;
41 static bool s_use_cea;
42 static unsigned s_num_buffers = 1;
44 static set<Crtc*> s_used_crtcs;
45 static set<Plane*> s_used_planes;
47 __attribute__ ((unused))
48 static void print_regex_match(smatch sm)
49 {
50         for (unsigned i = 0; i < sm.size(); ++i) {
51                 string str = sm[i].str();
52                 printf("%u: %s\n", i, str.c_str());
53         }
54 }
56 static void get_default_connector(Card& card, OutputInfo& output)
57 {
58         output.connector = card.get_first_connected_connector();
59         output.mode = output.connector->get_default_mode();
60 }
62 static void parse_connector(Card& card, const string& str, OutputInfo& output)
63 {
64         Connector* conn = nullptr;
66         auto connectors = card.get_connectors();
68         if (str[0] == '@') {
69                 char* endptr;
70                 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
71                 if (*endptr == 0) {
72                         if (idx >= connectors.size())
73                                 EXIT("Bad connector number '%u'", idx);
75                         conn = connectors[idx];
76                 }
77         } else {
78                 char* endptr;
79                 unsigned id = strtoul(str.c_str(), &endptr, 10);
80                 if (*endptr == 0) {
81                         Connector* c = card.get_connector(id);
82                         if (!c)
83                                 EXIT("Bad connector id '%u'", id);
85                         conn = c;
86                 }
87         }
89         if (!conn) {
90                 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
91                 if (iter != connectors.end())
92                         conn = *iter;
93         }
95         if (!conn)
96                 EXIT("No connector '%s'", str.c_str());
98         if (!conn->connected())
99                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
101         output.connector = conn;
102         output.mode = output.connector->get_default_mode();
105 static void get_default_crtc(Card& card, OutputInfo& output)
107         Crtc* crtc = output.connector->get_current_crtc();
109         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
110                 s_used_crtcs.insert(crtc);
111                 output.crtc = crtc;
112                 return;
113         }
115         for (const auto& possible : output.connector->get_possible_crtcs()) {
116                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
117                         s_used_crtcs.insert(possible);
118                         output.crtc = possible;
119                         return;
120                 }
121         }
123         EXIT("Could not find available crtc");
126 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
128         // @12:1920x1200@60
129         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
131         smatch sm;
132         if (!regex_match(crtc_str, sm, mode_re))
133                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
135         if (sm[2].matched) {
136                 bool use_idx = sm[1].length() == 1;
137                 unsigned num = stoul(sm[2].str());
139                 if (use_idx) {
140                         auto crtcs = card.get_crtcs();
142                         if (num >= crtcs.size())
143                                 EXIT("Bad crtc number '%u'", num);
145                         output.crtc = crtcs[num];
146                 } else {
147                         Crtc* c = card.get_crtc(num);
148                         if (!c)
149                                 EXIT("Bad crtc id '%u'", num);
151                         output.crtc = c;
152                 }
153         } else {
154                 output.crtc = output.connector->get_current_crtc();
155         }
157         unsigned w = stoul(sm[3]);
158         unsigned h = stoul(sm[4]);
159         bool ilace = sm[5].matched ? true : false;
160         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
162         bool found_mode = false;
164         try {
165                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
166                 found_mode = true;
167         } catch (exception& e) { }
169         if (!found_mode && s_use_dmt) {
170                 try {
171                         output.mode = find_dmt(w, h, refresh, ilace);
172                         found_mode = true;
173                         printf("Found mode from DMT\n");
174                 } catch (exception& e) { }
175         }
177         if (!found_mode && s_use_cea) {
178                 try {
179                         output.mode = find_cea(w, h, refresh, ilace);
180                         found_mode = true;
181                         printf("Found mode from CEA\n");
182                 } catch (exception& e) { }
183         }
185         if (!found_mode)
186                 throw invalid_argument("Mode not found");
189 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
191         // 3:400,400-400x400
192         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
194         smatch sm;
195         if (!regex_match(plane_str, sm, plane_re))
196                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
198         if (sm[2].matched) {
199                 bool use_idx = sm[1].length() == 1;
200                 unsigned num = stoul(sm[2].str());
202                 if (use_idx) {
203                         auto planes = card.get_planes();
205                         if (num >= planes.size())
206                                 EXIT("Bad plane number '%u'", num);
208                         pinfo.plane = planes[num];
209                 } else {
210                         Plane* p = card.get_plane(num);
211                         if (!p)
212                                 EXIT("Bad plane id '%u'", num);
214                         pinfo.plane = p;
215                 }
216         } else {
217                 for (Plane* p : output.crtc->get_possible_planes()) {
218                         if (s_used_planes.find(p) != s_used_planes.end())
219                                 continue;
221                         if (p->plane_type() != PlaneType::Overlay)
222                                 continue;
224                         pinfo.plane = p;
225                 }
227                 if (!pinfo.plane)
228                         EXIT("Failed to find available plane");
229         }
231         s_used_planes.insert(pinfo.plane);
233         pinfo.w = stoul(sm[5]);
234         pinfo.h = stoul(sm[6]);
236         if (sm[3].matched)
237                 pinfo.x = stoul(sm[3]);
238         else
239                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
241         if (sm[4].matched)
242                 pinfo.y = stoul(sm[4]);
243         else
244                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
247 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
249         vector<DumbFramebuffer*> v;
251         for (unsigned i = 0; i < s_num_buffers; ++i)
252                 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
254         return v;
257 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
259         unsigned w = def_w;
260         unsigned h = def_h;
261         PixelFormat format = PixelFormat::XRGB8888;
263         if (!fb_str.empty()) {
264                 // XXX the regexp is not quite correct
265                 // 400x400-NV12
266                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
268                 smatch sm;
269                 if (!regex_match(fb_str, sm, fb_re))
270                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
272                 if (sm[1].matched)
273                         w = stoul(sm[1]);
274                 if (sm[2].matched)
275                         h = stoul(sm[2]);
276                 if (sm[3].matched)
277                         format = FourCCToPixelFormat(sm[3]);
278         }
280         vector<DumbFramebuffer*> v;
282         for (unsigned i = 0; i < s_num_buffers; ++i)
283                 v.push_back(new DumbFramebuffer(card, w, h, format));
285         return v;
288 static const char* usage_str =
289                 "Usage: testpat [OPTION]...\n\n"
290                 "Show a test pattern on a display or plane\n\n"
291                 "Options:\n"
292                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
293                 "  -c, --connector=CONN      CONN is <connector>\n"
294                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
295                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
296                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
297                 "      --dmt                 Search for the given mode from DMT tables\n"
298                 "      --cea                 Search for the given mode from CEA tables\n"
299                 "\n"
300                 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
301                 "<connector> can also be given by name.\n"
302                 "\n"
303                 "Options can be given multiple times to set up multiple displays or planes.\n"
304                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
305                 "an earlier option.\n"
306                 "If you omit parameters, testpat tries to guess what you mean\n"
307                 "\n"
308                 "Examples:\n"
309                 "\n"
310                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
311                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
312                 "XR24 framebuffer on first connected connector in the default mode:\n"
313                 "    testpat -f XR24\n\n"
314                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
315                 "    testpat -p 400x400 -f XR24\n\n"
316                 "Test pattern on the second connector with default mode:\n"
317                 "    testpat -c @1\n"
318                 ;
320 static void usage()
322         puts(usage_str);
325 enum class ObjectType
327         Connector,
328         Crtc,
329         Plane,
330         Framebuffer,
331 };
333 struct Arg
335         ObjectType type;
336         string arg;
337 };
339 static string s_device_path = "/dev/dri/card0";
341 static vector<Arg> parse_cmdline(int argc, char **argv)
343         vector<Arg> args;
345         OptionSet optionset = {
346                 Option("|device=",
347                 [&](string s)
348                 {
349                         s_device_path = s;
350                 }),
351                 Option("c|connector=",
352                 [&](string s)
353                 {
354                         args.push_back(Arg { ObjectType::Connector, s });
355                 }),
356                 Option("r|crtc=", [&](string s)
357                 {
358                         args.push_back(Arg { ObjectType::Crtc, s });
359                 }),
360                 Option("p|plane=", [&](string s)
361                 {
362                         args.push_back(Arg { ObjectType::Plane, s });
363                 }),
364                 Option("f|fb=", [&](string s)
365                 {
366                         args.push_back(Arg { ObjectType::Framebuffer, s });
367                 }),
368                 Option("|dmt", []()
369                 {
370                         s_use_dmt = true;
371                 }),
372                 Option("|cea", []()
373                 {
374                         s_use_cea = true;
375                 }),
376                 Option("h|help", [&]()
377                 {
378                         usage();
379                         exit(-1);
380                 }),
381         };
383         optionset.parse(argc, argv);
385         if (optionset.params().size() > 0) {
386                 usage();
387                 exit(-1);
388         }
390         return args;
393 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
395         vector<OutputInfo> outputs;
397         if (output_args.size() == 0) {
398                 // no output args, show a pattern on all screens
399                 for (auto& pipe : card.get_connected_pipelines()) {
400                         OutputInfo output = { };
401                         output.connector = pipe.connector;
402                         output.crtc = pipe.crtc;
403                         output.mode = output.connector->get_default_mode();
405                         output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
407                         outputs.push_back(output);
408                 }
410                 return outputs;
411         }
413         OutputInfo* current_output = 0;
414         PlaneInfo* current_plane = 0;
416         for (auto& arg : output_args) {
417                 switch (arg.type) {
418                 case ObjectType::Connector:
419                 {
420                         outputs.push_back(OutputInfo { });
421                         current_output = &outputs.back();
423                         parse_connector(card, arg.arg, *current_output);
424                         current_plane = 0;
426                         break;
427                 }
429                 case ObjectType::Crtc:
430                 {
431                         if (!current_output) {
432                                 outputs.push_back(OutputInfo { });
433                                 current_output = &outputs.back();
434                         }
436                         if (!current_output->connector)
437                                 get_default_connector(card, *current_output);
439                         parse_crtc(card, arg.arg, *current_output);
441                         current_output->user_set_crtc = true;
443                         current_plane = 0;
445                         break;
446                 }
448                 case ObjectType::Plane:
449                 {
450                         if (!current_output) {
451                                 outputs.push_back(OutputInfo { });
452                                 current_output = &outputs.back();
453                         }
455                         if (!current_output->connector)
456                                 get_default_connector(card, *current_output);
458                         if (!current_output->crtc)
459                                 get_default_crtc(card, *current_output);
461                         current_output->planes.push_back(PlaneInfo { });
462                         current_plane = &current_output->planes.back();
464                         parse_plane(card, arg.arg, *current_output, *current_plane);
466                         break;
467                 }
469                 case ObjectType::Framebuffer:
470                 {
471                         if (!current_output) {
472                                 outputs.push_back(OutputInfo { });
473                                 current_output = &outputs.back();
474                         }
476                         if (!current_output->connector)
477                                 get_default_connector(card, *current_output);
479                         if (!current_output->crtc)
480                                 get_default_crtc(card, *current_output);
482                         int def_w, def_h;
484                         if (current_plane) {
485                                 def_w = current_plane->w;
486                                 def_h = current_plane->h;
487                         } else {
488                                 def_w = current_output->mode.hdisplay;
489                                 def_h = current_output->mode.vdisplay;
490                         }
492                         auto fbs = parse_fb(card, arg.arg, def_w, def_h);
494                         if (current_plane)
495                                 current_plane->fbs = fbs;
496                         else
497                                 current_output->fbs = fbs;
499                         break;
500                 }
501                 }
502         }
504         // create default framebuffers if needed
505         for (OutputInfo& o : outputs) {
506                 if (!o.crtc) {
507                         get_default_crtc(card, *current_output);
508                         o.user_set_crtc = true;
509                 }
511                 if (o.fbs.empty() && o.user_set_crtc)
512                         o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
514                 for (PlaneInfo &p : o.planes) {
515                         if (p.fbs.empty())
516                                 p.fbs = get_default_fb(card, p.w, p.h);
517                 }
518         }
520         return outputs;
523 static std::string videomode_to_string(const Videomode& mode)
525         unsigned hfp = mode.hsync_start - mode.hdisplay;
526         unsigned hsw = mode.hsync_end - mode.hsync_start;
527         unsigned hbp = mode.htotal - mode.hsync_end;
529         unsigned vfp = mode.vsync_start - mode.vdisplay;
530         unsigned vsw = mode.vsync_end - mode.vsync_start;
531         unsigned vbp = mode.vtotal - mode.vsync_end;
533         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
534         if (mode.flags & (1<<4)) // XXX interlace
535                 hz *= 2;
537         char buf[256];
539         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
540                 mode.clock / 1000.0,
541                 mode.hdisplay, hfp, hsw, hbp,
542                 mode.vdisplay, vfp, vsw, vbp,
543                 mode.vrefresh, hz);
545         return std::string(buf);
548 static void print_outputs(const vector<OutputInfo>& outputs)
550         for (unsigned i = 0; i < outputs.size(); ++i) {
551                 const OutputInfo& o = outputs[i];
553                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
554                        o.connector->fullname().c_str());
555                 printf("  Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
556                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
557                        videomode_to_string(o.mode).c_str());
558                 if (!o.fbs.empty()) {
559                         auto fb = o.fbs[0];
560                         printf("    Fb %ux%u-%s\n", fb->width(), fb->height(),
561                                PixelFormatToFourCC(fb->format()).c_str());
562                 }
564                 for (unsigned j = 0; j < o.planes.size(); ++j) {
565                         const PlaneInfo& p = o.planes[j];
566                         auto fb = p.fbs[0];
567                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
568                                p.x, p.y, p.w, p.h);
569                         printf("    Fb %ux%u-%s\n", fb->width(), fb->height(),
570                                PixelFormatToFourCC(fb->format()).c_str());
571                 }
572         }
575 static void draw_test_patterns(const vector<OutputInfo>& outputs)
577         for (const OutputInfo& o : outputs) {
578                 for (auto fb : o.fbs)
579                         draw_test_pattern(*fb);
581                 for (const PlaneInfo& p : o.planes)
582                         for (auto fb : p.fbs)
583                                 draw_test_pattern(*fb);
584         }
587 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
589         for (const OutputInfo& o : outputs) {
590                 auto conn = o.connector;
591                 auto crtc = o.crtc;
593                 if (!o.fbs.empty()) {
594                         auto fb = o.fbs[0];
595                         int r = crtc->set_mode(conn, *fb, o.mode);
596                         if (r)
597                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
598                                        crtc->id(), strerror(-r));
599                 }
601                 for (const PlaneInfo& p : o.planes) {
602                         auto fb = p.fbs[0];
603                         int r = crtc->set_plane(p.plane, *fb,
604                                                 p.x, p.y, p.w, p.h,
605                                                 0, 0, fb->width(), fb->height());
606                         if (r)
607                                 printf("crtc->set_plane() failed for plane %u: %s\n",
608                                        p.plane->id(), strerror(-r));
609                 }
610         }
613 int main(int argc, char **argv)
615         vector<Arg> output_args = parse_cmdline(argc, argv);
617         Card card(s_device_path);
619         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
621         draw_test_patterns(outputs);
623         print_outputs(outputs);
625         set_crtcs_n_planes(card, outputs);
627         printf("press enter to exit\n");
629         getchar();