testpat: show video mode Hz
[android/external-libkmsxx.git] / tests / testpat.cpp
1 #include <cstdio>
2 #include <algorithm>
3 #include <regex>
4 #include <set>
6 #include "kms++.h"
8 #include "test.h"
9 #include "opts.h"
11 using namespace std;
12 using namespace kms;
14 struct PlaneInfo
15 {
16         Plane* plane;
18         unsigned x;
19         unsigned y;
20         unsigned w;
21         unsigned h;
23         DumbFramebuffer* fb;
24 };
26 struct OutputInfo
27 {
28         Connector* connector;
30         Crtc* crtc;
31         Videomode mode;
32         bool user_set_crtc;
33         DumbFramebuffer* fb;
35         vector<PlaneInfo> planes;
36 };
38 static set<Crtc*> s_used_crtcs;
39 static set<Plane*> s_used_planes;
41 __attribute__ ((unused))
42 static void print_regex_match(smatch sm)
43 {
44         for (unsigned i = 0; i < sm.size(); ++i) {
45                 string str = sm[i].str();
46                 printf("%u: %s\n", i, str.c_str());
47         }
48 }
50 static void get_default_connector(Card& card, OutputInfo& output)
51 {
52         output.connector = card.get_first_connected_connector();
53         output.mode = output.connector->get_default_mode();
54 }
56 static void parse_connector(Card& card, const string& str, OutputInfo& output)
57 {
58         Connector* conn = nullptr;
60         auto connectors = card.get_connectors();
62         if (str[0] == '@') {
63                 char* endptr;
64                 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
65                 if (*endptr == 0) {
66                         if (idx >= connectors.size())
67                                 EXIT("Bad connector number '%u'", idx);
69                         conn = connectors[idx];
70                 }
71         } else {
72                 char* endptr;
73                 unsigned id = strtoul(str.c_str(), &endptr, 10);
74                 if (*endptr == 0) {
75                         Connector* c = card.get_connector(id);
76                         if (!c)
77                                 EXIT("Bad connector id '%u'", id);
79                         conn = c;
80                 }
81         }
83         if (!conn) {
84                 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
85                 if (iter != connectors.end())
86                         conn = *iter;
87         }
89         if (!conn)
90                 EXIT("No connector '%s'", str.c_str());
92         if (!conn->connected())
93                 EXIT("Connector '%s' not connected", conn->fullname().c_str());
95         output.connector = conn;
96         output.mode = output.connector->get_default_mode();
97 }
99 static void get_default_crtc(Card& card, OutputInfo& output)
101         Crtc* crtc = output.connector->get_current_crtc();
103         if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
104                 s_used_crtcs.insert(crtc);
105                 output.crtc = crtc;
106                 return;
107         }
109         for (const auto& possible : output.connector->get_possible_crtcs()) {
110                 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
111                         s_used_crtcs.insert(possible);
112                         output.crtc = possible;
113                         return;
114                 }
115         }
117         EXIT("Could not find available crtc");
120 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
122         // @12:1920x1200-60
123         const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:-(\\d+))?");
125         smatch sm;
126         if (!regex_match(crtc_str, sm, mode_re))
127                 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
129         if (sm[2].matched) {
130                 bool use_idx = sm[1].length() == 1;
131                 unsigned num = stoul(sm[2].str());
133                 if (use_idx) {
134                         auto crtcs = card.get_crtcs();
136                         if (num >= crtcs.size())
137                                 EXIT("Bad crtc number '%u'", num);
139                         output.crtc = crtcs[num];
140                 } else {
141                         Crtc* c = card.get_crtc(num);
142                         if (!c)
143                                 EXIT("Bad crtc id '%u'", num);
145                         output.crtc = c;
146                 }
147         } else {
148                 output.crtc = output.connector->get_current_crtc();
149         }
151         unsigned w = stoul(sm[3]);
152         unsigned h = stoul(sm[4]);
153         bool ilace = sm[5].matched ? true : false;
154         unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
156         output.mode = output.connector->get_mode(w, h, refresh, ilace);
159 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
161         // 3:400,400-400x400
162         const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
164         smatch sm;
165         if (!regex_match(plane_str, sm, plane_re))
166                 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
168         if (sm[2].matched) {
169                 bool use_idx = sm[1].length() == 1;
170                 unsigned num = stoul(sm[2].str());
172                 if (use_idx) {
173                         auto planes = card.get_planes();
175                         if (num >= planes.size())
176                                 EXIT("Bad plane number '%u'", num);
178                         pinfo.plane = planes[num];
179                 } else {
180                         Plane* p = card.get_plane(num);
181                         if (!p)
182                                 EXIT("Bad plane id '%u'", num);
184                         pinfo.plane = p;
185                 }
186         } else {
187                 for (Plane* p : output.crtc->get_possible_planes()) {
188                         if (s_used_planes.find(p) != s_used_planes.end())
189                                 continue;
191                         if (p->plane_type() != PlaneType::Overlay)
192                                 continue;
194                         pinfo.plane = p;
195                 }
197                 if (!pinfo.plane)
198                         EXIT("Failed to find available plane");
199         }
201         s_used_planes.insert(pinfo.plane);
203         pinfo.w = stoul(sm[5]);
204         pinfo.h = stoul(sm[6]);
206         if (sm[3].matched)
207                 pinfo.x = stoul(sm[3]);
208         else
209                 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
211         if (sm[4].matched)
212                 pinfo.y = stoul(sm[4]);
213         else
214                 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
217 static DumbFramebuffer* get_default_fb(Card& card, unsigned width, unsigned height)
219         auto fb = new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888);
220         draw_test_pattern(*fb);
221         return fb;
224 static DumbFramebuffer* parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
226         unsigned w = def_w;
227         unsigned h = def_h;
228         PixelFormat format = PixelFormat::XRGB8888;
230         if (!fb_str.empty()) {
231                 // XXX the regexp is not quite correct
232                 // 400x400-NV12
233                 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
235                 smatch sm;
236                 if (!regex_match(fb_str, sm, fb_re))
237                         EXIT("Failed to parse fb option '%s'", fb_str.c_str());
239                 if (sm[1].matched)
240                         w = stoul(sm[1]);
241                 if (sm[2].matched)
242                         h = stoul(sm[2]);
243                 if (sm[3].matched)
244                         format = FourCCToPixelFormat(sm[3]);
245         }
247         auto fb = new DumbFramebuffer(card, w, h, format);
248         draw_test_pattern(*fb);
249         return fb;
252 static const char* usage_str =
253                 "Usage: testpat [OPTION]...\n\n"
254                 "Show a test pattern on a display or plane\n\n"
255                 "Options:\n"
256                 "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
257                 "  -c, --connector=CONN      CONN is <connector>\n"
258                 "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
259                 "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
260                 "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
261                 "\n"
262                 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
263                 "<connector> can also be given by name.\n"
264                 "\n"
265                 "Options can be given multiple times to set up multiple displays or planes.\n"
266                 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
267                 "an earlier option.\n"
268                 "If you omit parameters, testpat tries to guess what you mean\n"
269                 "\n"
270                 "Examples:\n"
271                 "\n"
272                 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
273                 "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
274                 "XR24 framebuffer on first connected connector in the default mode:\n"
275                 "    testpat -f XR24\n\n"
276                 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
277                 "    testpat -p 400x400 -f XR24\n\n"
278                 "Test pattern on the second connector with default mode:\n"
279                 "    testpat -c @1\n"
280                 ;
282 static void usage()
284         puts(usage_str);
287 enum class ObjectType
289         Connector,
290         Crtc,
291         Plane,
292         Framebuffer,
293 };
295 struct Arg
297         ObjectType type;
298         string arg;
299 };
301 static string s_device_path = "/dev/dri/card0";
303 static vector<Arg> parse_cmdline(int argc, char **argv)
305         vector<Arg> args;
307         OptionSet optionset = {
308                 Option("|device=",
309                 [&](string s)
310                 {
311                         s_device_path = s;
312                 }),
313                 Option("c|connector=",
314                 [&](string s)
315                 {
316                         args.push_back(Arg { ObjectType::Connector, s });
317                 }),
318                 Option("r|crtc=", [&](string s)
319                 {
320                         args.push_back(Arg { ObjectType::Crtc, s });
321                 }),
322                 Option("p|plane=", [&](string s)
323                 {
324                         args.push_back(Arg { ObjectType::Plane, s });
325                 }),
326                 Option("f|fb=", [&](string s)
327                 {
328                         args.push_back(Arg { ObjectType::Framebuffer, s });
329                 }),
330                 Option("h|help", [&]()
331                 {
332                         usage();
333                         exit(-1);
334                 }),
335         };
337         optionset.parse(argc, argv);
339         if (optionset.params().size() > 0) {
340                 usage();
341                 exit(-1);
342         }
344         return args;
347 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
349         vector<OutputInfo> outputs;
351         if (output_args.size() == 0) {
352                 // no output args, show a pattern on all screens
353                 for (auto& pipe : card.get_connected_pipelines()) {
354                         OutputInfo output = { };
355                         output.connector = pipe.connector;
356                         output.crtc = pipe.crtc;
357                         output.mode = output.connector->get_default_mode();
359                         output.fb = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
361                         outputs.push_back(output);
362                 }
364                 return outputs;
365         }
367         OutputInfo* current_output = 0;
368         PlaneInfo* current_plane = 0;
370         for (auto& arg : output_args) {
371                 switch (arg.type) {
372                 case ObjectType::Connector:
373                 {
374                         outputs.push_back(OutputInfo { });
375                         current_output = &outputs.back();
377                         parse_connector(card, arg.arg, *current_output);
378                         current_plane = 0;
380                         break;
381                 }
383                 case ObjectType::Crtc:
384                 {
385                         if (!current_output) {
386                                 outputs.push_back(OutputInfo { });
387                                 current_output = &outputs.back();
388                         }
390                         if (!current_output->connector)
391                                 get_default_connector(card, *current_output);
393                         parse_crtc(card, arg.arg, *current_output);
395                         current_output->user_set_crtc = true;
397                         current_plane = 0;
399                         break;
400                 }
402                 case ObjectType::Plane:
403                 {
404                         if (!current_output) {
405                                 outputs.push_back(OutputInfo { });
406                                 current_output = &outputs.back();
407                         }
409                         if (!current_output->connector)
410                                 get_default_connector(card, *current_output);
412                         if (!current_output->crtc)
413                                 get_default_crtc(card, *current_output);
415                         current_output->planes.push_back(PlaneInfo { });
416                         current_plane = &current_output->planes.back();
418                         parse_plane(card, arg.arg, *current_output, *current_plane);
420                         break;
421                 }
423                 case ObjectType::Framebuffer:
424                 {
425                         if (!current_output) {
426                                 outputs.push_back(OutputInfo { });
427                                 current_output = &outputs.back();
428                         }
430                         if (!current_output->connector)
431                                 get_default_connector(card, *current_output);
433                         if (!current_output->crtc)
434                                 get_default_crtc(card, *current_output);
436                         int def_w, def_h;
438                         if (current_plane) {
439                                 def_w = current_plane->w;
440                                 def_h = current_plane->h;
441                         } else {
442                                 def_w = current_output->mode.hdisplay;
443                                 def_h = current_output->mode.vdisplay;
444                         }
446                         auto fb = parse_fb(card, arg.arg, def_w, def_h);
448                         if (current_plane)
449                                 current_plane->fb = fb;
450                         else
451                                 current_output->fb = fb;
453                         break;
454                 }
455                 }
456         }
458         // create default framebuffers if needed
459         for (OutputInfo& o : outputs) {
460                 if (!o.crtc) {
461                         get_default_crtc(card, *current_output);
462                         o.user_set_crtc = true;
463                 }
465                 if (!o.fb && o.user_set_crtc)
466                         o.fb = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
468                 for (PlaneInfo &p : o.planes) {
469                         if (!p.fb)
470                                 p.fb = get_default_fb(card, p.w, p.h);
471                 }
472         }
474         return outputs;
477 static std::string videomode_to_string(const Videomode& mode)
479         unsigned hfp = mode.hsync_start - mode.hdisplay;
480         unsigned hsw = mode.hsync_end - mode.hsync_start;
481         unsigned hbp = mode.htotal - mode.hsync_end;
483         unsigned vfp = mode.vsync_start - mode.vdisplay;
484         unsigned vsw = mode.vsync_end - mode.vsync_start;
485         unsigned vbp = mode.vtotal - mode.vsync_end;
487         float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
488         if (mode.flags & (1<<4)) // XXX interlace
489                 hz *= 2;
491         char buf[256];
493         sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
494                 mode.clock / 1000.0,
495                 mode.hdisplay, hfp, hsw, hbp,
496                 mode.vdisplay, vfp, vsw, vbp,
497                 mode.vrefresh, hz);
499         return std::string(buf);
502 static void print_outputs(const vector<OutputInfo>& outputs)
504         for (unsigned i = 0; i < outputs.size(); ++i) {
505                 const OutputInfo& o = outputs[i];
507                 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
508                        o.connector->fullname().c_str());
509                 printf("  Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
510                        o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
511                        videomode_to_string(o.mode).c_str());
512                 if (o.fb)
513                         printf("    Fb %ux%u-%s\n", o.fb->width(), o.fb->height(),
514                                PixelFormatToFourCC(o.fb->format()).c_str());
516                 for (unsigned j = 0; j < o.planes.size(); ++j) {
517                         const PlaneInfo& p = o.planes[j];
518                         printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
519                                p.x, p.y, p.w, p.h);
520                         printf("    Fb %ux%u-%s\n", p.fb->width(), p.fb->height(),
521                                PixelFormatToFourCC(p.fb->format()).c_str());
522                 }
523         }
526 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
528         for (const OutputInfo& o : outputs) {
529                 auto conn = o.connector;
530                 auto crtc = o.crtc;
532                 if (o.fb) {
533                         int r = crtc->set_mode(conn, *o.fb, o.mode);
534                         if (r)
535                                 printf("crtc->set_mode() failed for crtc %u: %s\n",
536                                        crtc->id(), strerror(-r));
537                 }
539                 for (const PlaneInfo& p : o.planes) {
540                         int r = crtc->set_plane(p.plane, *p.fb,
541                                                 p.x, p.y, p.w, p.h,
542                                                 0, 0, p.fb->width(), p.fb->height());
543                         if (r)
544                                 printf("crtc->set_plane() failed for plane %u: %s\n",
545                                        p.plane->id(), strerror(-r));
546                 }
547         }
550 int main(int argc, char **argv)
552         vector<Arg> output_args = parse_cmdline(argc, argv);
554         Card card(s_device_path);
556         vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
558         print_outputs(outputs);
560         set_crtcs_n_planes(card, outputs);
562         printf("press enter to exit\n");
564         getchar();