0dc7c1735a7de2ba93e4e36fe8ceb8f8984ebe28
[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         DumbFramebuffer* fb;
26 };
28 struct OutputInfo
29 {
30         Connector* connector;
32         Crtc* crtc;
33         Videomode mode;
34         bool user_set_crtc;
35         DumbFramebuffer* fb;
37         vector<PlaneInfo> planes;
38 };
40 static bool s_use_dmt;
41 static bool s_use_cea;
43 static set<Crtc*> s_used_crtcs;
44 static set<Plane*> s_used_planes;
46 __attribute__ ((unused))
47 static void print_regex_match(smatch sm)
48 {
49         for (unsigned i = 0; i < sm.size(); ++i) {
50                 string str = sm[i].str();
51                 printf("%u: %s\n", i, str.c_str());
52         }
53 }
55 static void get_default_connector(Card& card, OutputInfo& output)
56 {
57         output.connector = card.get_first_connected_connector();
58         output.mode = output.connector->get_default_mode();
59 }
61 static void parse_connector(Card& card, const string& str, OutputInfo& output)
62 {
63         Connector* conn = nullptr;
65         auto connectors = card.get_connectors();
67         if (str[0] == '@') {
68                 char* endptr;
69                 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
70                 if (*endptr == 0) {
71                         if (idx >= connectors.size())
72                                 EXIT("Bad connector number '%u'", idx);
74                         conn = connectors[idx];
75                 }
76         } else {
77                 char* endptr;
78                 unsigned id = strtoul(str.c_str(), &endptr, 10);
79                 if (*endptr == 0) {
80                         Connector* c = card.get_connector(id);
81                         if (!c)
82                                 EXIT("Bad connector id '%u'", id);
84                         conn = c;
85                 }
86         }
88         if (!conn) {
89                 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
90                 if (iter != connectors.end())
91                         conn = *iter;
92         }
94         if (!conn)
95                 EXIT("No connector '%s'", str.c_str());
97         if (!conn->connected())
98                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
100         output.connector = conn;
101         output.mode = output.connector->get_default_mode();
104 static void get_default_crtc(Card& card, OutputInfo& output)
106         Crtc* crtc = output.connector->get_current_crtc();
108         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
109                 s_used_crtcs.insert(crtc);
110                 output.crtc = crtc;
111                 return;
112         }
114         for (const auto& possible : output.connector->get_possible_crtcs()) {
115                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
116                         s_used_crtcs.insert(possible);
117                         output.crtc = possible;
118                         return;
119                 }
120         }
122         EXIT("Could not find available crtc");
125 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
127         // @12:1920x1200@60
128         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
130         smatch sm;
131         if (!regex_match(crtc_str, sm, mode_re))
132                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
134         if (sm[2].matched) {
135                 bool use_idx = sm[1].length() == 1;
136                 unsigned num = stoul(sm[2].str());
138                 if (use_idx) {
139                         auto crtcs = card.get_crtcs();
141                         if (num >= crtcs.size())
142                                 EXIT("Bad crtc number '%u'", num);
144                         output.crtc = crtcs[num];
145                 } else {
146                         Crtc* c = card.get_crtc(num);
147                         if (!c)
148                                 EXIT("Bad crtc id '%u'", num);
150                         output.crtc = c;
151                 }
152         } else {
153                 output.crtc = output.connector->get_current_crtc();
154         }
156         unsigned w = stoul(sm[3]);
157         unsigned h = stoul(sm[4]);
158         bool ilace = sm[5].matched ? true : false;
159         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
161         bool found_mode = false;
163         try {
164                 output.mode = output.connector->get_mode(w, h, refresh, ilace);
165                 found_mode = true;
166         } catch (exception& e) { }
168         if (!found_mode && s_use_dmt) {
169                 try {
170                         output.mode = find_dmt(w, h, refresh, ilace);
171                         found_mode = true;
172                         printf("Found mode from DMT\n");
173                 } catch (exception& e) { }
174         }
176         if (!found_mode && s_use_cea) {
177                 try {
178                         output.mode = find_cea(w, h, refresh, ilace);
179                         found_mode = true;
180                         printf("Found mode from CEA\n");
181                 } catch (exception& e) { }
182         }
184         if (!found_mode)
185                 throw invalid_argument("Mode not found");
188 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
190         // 3:400,400-400x400
191         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
193         smatch sm;
194         if (!regex_match(plane_str, sm, plane_re))
195                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
197         if (sm[2].matched) {
198                 bool use_idx = sm[1].length() == 1;
199                 unsigned num = stoul(sm[2].str());
201                 if (use_idx) {
202                         auto planes = card.get_planes();
204                         if (num >= planes.size())
205                                 EXIT("Bad plane number '%u'", num);
207                         pinfo.plane = planes[num];
208                 } else {
209                         Plane* p = card.get_plane(num);
210                         if (!p)
211                                 EXIT("Bad plane id '%u'", num);
213                         pinfo.plane = p;
214                 }
215         } else {
216                 for (Plane* p : output.crtc->get_possible_planes()) {
217                         if (s_used_planes.find(p) != s_used_planes.end())
218                                 continue;
220                         if (p->plane_type() != PlaneType::Overlay)
221                                 continue;
223                         pinfo.plane = p;
224                 }
226                 if (!pinfo.plane)
227                         EXIT("Failed to find available plane");
228         }
230         s_used_planes.insert(pinfo.plane);
232         pinfo.w = stoul(sm[5]);
233         pinfo.h = stoul(sm[6]);
235         if (sm[3].matched)
236                 pinfo.x = stoul(sm[3]);
237         else
238                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
240         if (sm[4].matched)
241                 pinfo.y = stoul(sm[4]);
242         else
243                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
246 static DumbFramebuffer* get_default_fb(Card& card, unsigned width, unsigned height)
248         return new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888);
251 static DumbFramebuffer* parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
253         unsigned w = def_w;
254         unsigned h = def_h;
255         PixelFormat format = PixelFormat::XRGB8888;
257         if (!fb_str.empty()) {
258                 // XXX the regexp is not quite correct
259                 // 400x400-NV12
260                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
262                 smatch sm;
263                 if (!regex_match(fb_str, sm, fb_re))
264                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
266                 if (sm[1].matched)
267                         w = stoul(sm[1]);
268                 if (sm[2].matched)
269                         h = stoul(sm[2]);
270                 if (sm[3].matched)
271                         format = FourCCToPixelFormat(sm[3]);
272         }
274         return new DumbFramebuffer(card, w, h, format);
277 static const char* usage_str =
278                 "Usage: testpat [OPTION]...\n\n"
279                 "Show a test pattern on a display or plane\n\n"
280                 "Options:\n"
281                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
282                 "  -c, --connector=CONN      CONN is <connector>\n"
283                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
284                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
285                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
286                 "      --dmt                 Search for the given mode from DMT tables\n"
287                 "      --cea                 Search for the given mode from CEA tables\n"
288                 "\n"
289                 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
290                 "<connector> can also be given by name.\n"
291                 "\n"
292                 "Options can be given multiple times to set up multiple displays or planes.\n"
293                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
294                 "an earlier option.\n"
295                 "If you omit parameters, testpat tries to guess what you mean\n"
296                 "\n"
297                 "Examples:\n"
298                 "\n"
299                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
300                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
301                 "XR24 framebuffer on first connected connector in the default mode:\n"
302                 "    testpat -f XR24\n\n"
303                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
304                 "    testpat -p 400x400 -f XR24\n\n"
305                 "Test pattern on the second connector with default mode:\n"
306                 "    testpat -c @1\n"
307                 ;
309 static void usage()
311         puts(usage_str);
314 enum class ObjectType
316         Connector,
317         Crtc,
318         Plane,
319         Framebuffer,
320 };
322 struct Arg
324         ObjectType type;
325         string arg;
326 };
328 static string s_device_path = "/dev/dri/card0";
330 static vector<Arg> parse_cmdline(int argc, char **argv)
332         vector<Arg> args;
334         OptionSet optionset = {
335                 Option("|device=",
336                 [&](string s)
337                 {
338                         s_device_path = s;
339                 }),
340                 Option("c|connector=",
341                 [&](string s)
342                 {
343                         args.push_back(Arg { ObjectType::Connector, s });
344                 }),
345                 Option("r|crtc=", [&](string s)
346                 {
347                         args.push_back(Arg { ObjectType::Crtc, s });
348                 }),
349                 Option("p|plane=", [&](string s)
350                 {
351                         args.push_back(Arg { ObjectType::Plane, s });
352                 }),
353                 Option("f|fb=", [&](string s)
354                 {
355                         args.push_back(Arg { ObjectType::Framebuffer, s });
356                 }),
357                 Option("|dmt", []()
358                 {
359                         s_use_dmt = true;
360                 }),
361                 Option("|cea", []()
362                 {
363                         s_use_cea = true;
364                 }),
365                 Option("h|help", [&]()
366                 {
367                         usage();
368                         exit(-1);
369                 }),
370         };
372         optionset.parse(argc, argv);
374         if (optionset.params().size() > 0) {
375                 usage();
376                 exit(-1);
377         }
379         return args;
382 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
384         vector<OutputInfo> outputs;
386         if (output_args.size() == 0) {
387                 // no output args, show a pattern on all screens
388                 for (auto& pipe : card.get_connected_pipelines()) {
389                         OutputInfo output = { };
390                         output.connector = pipe.connector;
391                         output.crtc = pipe.crtc;
392                         output.mode = output.connector->get_default_mode();
394                         output.fb = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
396                         outputs.push_back(output);
397                 }
399                 return outputs;
400         }
402         OutputInfo* current_output = 0;
403         PlaneInfo* current_plane = 0;
405         for (auto& arg : output_args) {
406                 switch (arg.type) {
407                 case ObjectType::Connector:
408                 {
409                         outputs.push_back(OutputInfo { });
410                         current_output = &outputs.back();
412                         parse_connector(card, arg.arg, *current_output);
413                         current_plane = 0;
415                         break;
416                 }
418                 case ObjectType::Crtc:
419                 {
420                         if (!current_output) {
421                                 outputs.push_back(OutputInfo { });
422                                 current_output = &outputs.back();
423                         }
425                         if (!current_output->connector)
426                                 get_default_connector(card, *current_output);
428                         parse_crtc(card, arg.arg, *current_output);
430                         current_output->user_set_crtc = true;
432                         current_plane = 0;
434                         break;
435                 }
437                 case ObjectType::Plane:
438                 {
439                         if (!current_output) {
440                                 outputs.push_back(OutputInfo { });
441                                 current_output = &outputs.back();
442                         }
444                         if (!current_output->connector)
445                                 get_default_connector(card, *current_output);
447                         if (!current_output->crtc)
448                                 get_default_crtc(card, *current_output);
450                         current_output->planes.push_back(PlaneInfo { });
451                         current_plane = &current_output->planes.back();
453                         parse_plane(card, arg.arg, *current_output, *current_plane);
455                         break;
456                 }
458                 case ObjectType::Framebuffer:
459                 {
460                         if (!current_output) {
461                                 outputs.push_back(OutputInfo { });
462                                 current_output = &outputs.back();
463                         }
465                         if (!current_output->connector)
466                                 get_default_connector(card, *current_output);
468                         if (!current_output->crtc)
469                                 get_default_crtc(card, *current_output);
471                         int def_w, def_h;
473                         if (current_plane) {
474                                 def_w = current_plane->w;
475                                 def_h = current_plane->h;
476                         } else {
477                                 def_w = current_output->mode.hdisplay;
478                                 def_h = current_output->mode.vdisplay;
479                         }
481                         auto fb = parse_fb(card, arg.arg, def_w, def_h);
483                         if (current_plane)
484                                 current_plane->fb = fb;
485                         else
486                                 current_output->fb = fb;
488                         break;
489                 }
490                 }
491         }
493         // create default framebuffers if needed
494         for (OutputInfo& o : outputs) {
495                 if (!o.crtc) {
496                         get_default_crtc(card, *current_output);
497                         o.user_set_crtc = true;
498                 }
500                 if (!o.fb && o.user_set_crtc)
501                         o.fb = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
503                 for (PlaneInfo &p : o.planes) {
504                         if (!p.fb)
505                                 p.fb = get_default_fb(card, p.w, p.h);
506                 }
507         }
509         return outputs;
512 static std::string videomode_to_string(const Videomode& mode)
514         unsigned hfp = mode.hsync_start - mode.hdisplay;
515         unsigned hsw = mode.hsync_end - mode.hsync_start;
516         unsigned hbp = mode.htotal - mode.hsync_end;
518         unsigned vfp = mode.vsync_start - mode.vdisplay;
519         unsigned vsw = mode.vsync_end - mode.vsync_start;
520         unsigned vbp = mode.vtotal - mode.vsync_end;
522         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
523         if (mode.flags & (1<<4)) // XXX interlace
524                 hz *= 2;
526         char buf[256];
528         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
529                 mode.clock / 1000.0,
530                 mode.hdisplay, hfp, hsw, hbp,
531                 mode.vdisplay, vfp, vsw, vbp,
532                 mode.vrefresh, hz);
534         return std::string(buf);
537 static void print_outputs(const vector<OutputInfo>& outputs)
539         for (unsigned i = 0; i < outputs.size(); ++i) {
540                 const OutputInfo& o = outputs[i];
542                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
543                        o.connector->fullname().c_str());
544                 printf("  Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
545                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
546                        videomode_to_string(o.mode).c_str());
547                 if (o.fb)
548                         printf("    Fb %ux%u-%s\n", o.fb->width(), o.fb->height(),
549                                PixelFormatToFourCC(o.fb->format()).c_str());
551                 for (unsigned j = 0; j < o.planes.size(); ++j) {
552                         const PlaneInfo& p = o.planes[j];
553                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
554                                p.x, p.y, p.w, p.h);
555                         printf("    Fb %ux%u-%s\n", p.fb->width(), p.fb->height(),
556                                PixelFormatToFourCC(p.fb->format()).c_str());
557                 }
558         }
561 static void draw_test_patterns(const vector<OutputInfo>& outputs)
563         for (const OutputInfo& o : outputs) {
564                 if (o.fb)
565                         draw_test_pattern(*o.fb);
567                 for (const PlaneInfo& p : o.planes)
568                         draw_test_pattern(*p.fb);
569         }
572 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
574         for (const OutputInfo& o : outputs) {
575                 auto conn = o.connector;
576                 auto crtc = o.crtc;
578                 if (o.fb) {
579                         int r = crtc->set_mode(conn, *o.fb, o.mode);
580                         if (r)
581                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
582                                        crtc->id(), strerror(-r));
583                 }
585                 for (const PlaneInfo& p : o.planes) {
586                         int r = crtc->set_plane(p.plane, *p.fb,
587                                                 p.x, p.y, p.w, p.h,
588                                                 0, 0, p.fb->width(), p.fb->height());
589                         if (r)
590                                 printf("crtc->set_plane() failed for plane %u: %s\n",
591                                        p.plane->id(), strerror(-r));
592                 }
593         }
596 int main(int argc, char **argv)
598         vector<Arg> output_args = parse_cmdline(argc, argv);
600         Card card(s_device_path);
602         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
604         draw_test_patterns(outputs);
606         print_outputs(outputs);
608         set_crtcs_n_planes(card, outputs);
610         printf("press enter to exit\n");
612         getchar();