0f763c0063b2a11fad2e5bb23e28473c90baf2c4
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
7 #include <cstdint>
8 #include <cinttypes>
10 #include <sys/select.h>
12 #include <kms++/kms++.h>
13 #include <kms++/modedb.h>
14 #include <kms++/mode_cvt.h>
16 #include <kms++util/kms++util.h>
18 using namespace std;
19 using namespace kms;
21 struct PropInfo {
22 PropInfo(Property *p, uint64_t v) : prop(p), val(v) {}
24 Property *prop;
25 uint64_t val;
26 };
28 struct PlaneInfo
29 {
30 Plane* plane;
32 unsigned x;
33 unsigned y;
34 unsigned w;
35 unsigned h;
37 unsigned view_x;
38 unsigned view_y;
39 unsigned view_w;
40 unsigned view_h;
42 vector<Framebuffer*> fbs;
44 vector<PropInfo> props;
45 };
47 struct OutputInfo
48 {
49 Connector* connector;
51 Crtc* crtc;
52 Plane* primary_plane;
53 Videomode mode;
54 bool user_set_crtc;
55 vector<Framebuffer*> fbs;
57 vector<PlaneInfo> planes;
59 vector<PropInfo> conn_props;
60 vector<PropInfo> crtc_props;
61 };
63 static bool s_use_dmt;
64 static bool s_use_cea;
65 static unsigned s_num_buffers = 1;
66 static bool s_flip_mode;
67 static bool s_flip_sync;
68 static bool s_cvt;
69 static bool s_cvt_v2;
70 static bool s_cvt_vid_opt;
71 static unsigned s_max_flips;
73 __attribute__ ((unused))
74 static void print_regex_match(smatch sm)
75 {
76 for (unsigned i = 0; i < sm.size(); ++i) {
77 string str = sm[i].str();
78 printf("%u: %s\n", i, str.c_str());
79 }
80 }
82 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
83 {
84 Connector* conn = resman.reserve_connector(str);
86 if (!conn)
87 EXIT("No connector '%s'", str.c_str());
89 if (!conn->connected())
90 EXIT("Connector '%s' not connected", conn->fullname().c_str());
92 output.connector = conn;
93 output.mode = output.connector->get_default_mode();
94 }
96 static void get_default_crtc(ResourceManager& resman, OutputInfo& output)
97 {
98 output.crtc = resman.reserve_crtc(output.connector);
100 if (!output.crtc)
101 EXIT("Could not find available crtc");
102 }
104 static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output)
105 {
106 // @12:1920x1200i@60
107 // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
109 const regex modename_re("(?:(@?)(\\d+):)?" // @12:
110 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
111 "(?:@([\\d\\.]+))?"); // @60
113 const regex modeline_re("(?:(@?)(\\d+):)?" // @12:
114 "(\\d+)," // 33000000,
115 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])," // 800/210/30/16/-,
116 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])" // 480/22/13/10/-
117 "(?:,([i]+))?" // ,i
118 );
120 smatch sm;
121 if (regex_match(crtc_str, sm, modename_re)) {
122 if (sm[2].matched) {
123 bool use_id = sm[1].length() == 1;
124 unsigned num = stoul(sm[2].str());
126 if (use_id) {
127 Crtc* c = card.get_crtc(num);
128 if (!c)
129 EXIT("Bad crtc id '%u'", num);
131 output.crtc = c;
132 } else {
133 auto crtcs = card.get_crtcs();
135 if (num >= crtcs.size())
136 EXIT("Bad crtc number '%u'", num);
138 output.crtc = crtcs[num];
139 }
140 } else {
141 output.crtc = output.connector->get_current_crtc();
142 }
144 unsigned w = stoul(sm[3]);
145 unsigned h = stoul(sm[4]);
146 bool ilace = sm[5].matched ? true : false;
147 float refresh = sm[6].matched ? stof(sm[6]) : 0;
149 if (s_cvt) {
150 output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
151 } else if (s_use_dmt) {
152 try {
153 output.mode = find_dmt(w, h, refresh, ilace);
154 } catch (exception& e) {
155 EXIT("Mode not found from DMT tables\n");
156 }
157 } else if (s_use_cea) {
158 try {
159 output.mode = find_cea(w, h, refresh, ilace);
160 } catch (exception& e) {
161 EXIT("Mode not found from CEA tables\n");
162 }
163 } else {
164 try {
165 output.mode = output.connector->get_mode(w, h, refresh, ilace);
166 } catch (exception& e) {
167 EXIT("Mode not found from the connector\n");
168 }
169 }
170 } else if (regex_match(crtc_str, sm, modeline_re)) {
171 if (sm[2].matched) {
172 bool use_id = sm[1].length() == 1;
173 unsigned num = stoul(sm[2].str());
175 if (use_id) {
176 Crtc* c = card.get_crtc(num);
177 if (!c)
178 EXIT("Bad crtc id '%u'", num);
180 output.crtc = c;
181 } else {
182 auto crtcs = card.get_crtcs();
184 if (num >= crtcs.size())
185 EXIT("Bad crtc number '%u'", num);
187 output.crtc = crtcs[num];
188 }
189 } else {
190 output.crtc = output.connector->get_current_crtc();
191 }
193 unsigned clock = stoul(sm[3]);
195 unsigned hact = stoul(sm[4]);
196 unsigned hfp = stoul(sm[5]);
197 unsigned hsw = stoul(sm[6]);
198 unsigned hbp = stoul(sm[7]);
199 bool h_pos_sync = sm[8] == "+" ? true : false;
201 unsigned vact = stoul(sm[9]);
202 unsigned vfp = stoul(sm[10]);
203 unsigned vsw = stoul(sm[11]);
204 unsigned vbp = stoul(sm[12]);
205 bool v_pos_sync = sm[13] == "+" ? true : false;
207 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
208 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
209 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
211 if (sm[14].matched) {
212 for (int i = 0; i < sm[14].length(); ++i) {
213 char f = string(sm[14])[i];
215 switch (f) {
216 case 'i':
217 output.mode.set_interlace(true);
218 break;
219 default:
220 EXIT("Bad mode flag %c", f);
221 }
222 }
223 }
224 } else {
225 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
226 }
228 if (!resman.reserve_crtc(output.crtc))
229 EXIT("Could not find available crtc");
230 }
232 static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
233 {
234 // 3:400,400-400x400
235 const regex plane_re("(?:(@?)(\\d+):)?" // 3:
236 "(?:(\\d+),(\\d+)-)?" // 400,400-
237 "(\\d+)x(\\d+)"); // 400x400
239 smatch sm;
240 if (!regex_match(plane_str, sm, plane_re))
241 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
243 if (sm[2].matched) {
244 bool use_id = sm[1].length() == 1;
245 unsigned num = stoul(sm[2].str());
247 if (use_id) {
248 Plane* p = card.get_plane(num);
249 if (!p)
250 EXIT("Bad plane id '%u'", num);
252 pinfo.plane = p;
253 } else {
254 auto planes = card.get_planes();
256 if (num >= planes.size())
257 EXIT("Bad plane number '%u'", num);
259 pinfo.plane = planes[num];
260 }
262 pinfo.plane = resman.reserve_plane(pinfo.plane);
263 } else {
264 pinfo.plane = resman.reserve_overlay_plane(output.crtc);
265 }
267 if (!pinfo.plane)
268 EXIT("Failed to find available plane");
270 pinfo.w = stoul(sm[5]);
271 pinfo.h = stoul(sm[6]);
273 if (sm[3].matched)
274 pinfo.x = stoul(sm[3]);
275 else
276 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
278 if (sm[4].matched)
279 pinfo.y = stoul(sm[4]);
280 else
281 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
282 }
284 static void parse_prop(Card& card, const string& prop_str, vector<PropInfo> &props, const DrmPropObject* propobj)
285 {
286 string name, val;
287 Property* prop;
289 size_t split = prop_str.find("=");
291 if (split == string::npos)
292 EXIT("Equal sign ('=') not found in %s", prop_str.c_str());
294 name = prop_str.substr(0, split);
295 val = prop_str.substr(split+1);
296 prop = propobj->get_prop(name);
298 props.push_back(PropInfo(prop, stoull(val, 0, 0)));
299 }
301 static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
302 {
303 vector<Framebuffer*> v;
305 for (unsigned i = 0; i < s_num_buffers; ++i)
306 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
308 return v;
309 }
311 static vector<Framebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
312 {
313 unsigned w = def_w;
314 unsigned h = def_h;
315 PixelFormat format = PixelFormat::XRGB8888;
317 if (!fb_str.empty()) {
318 // XXX the regexp is not quite correct
319 // 400x400-NV12
320 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400
321 "(?:-)?" // -
322 "(\\w\\w\\w\\w)?"); // NV12
324 smatch sm;
325 if (!regex_match(fb_str, sm, fb_re))
326 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
328 if (sm[1].matched)
329 w = stoul(sm[1]);
330 if (sm[2].matched)
331 h = stoul(sm[2]);
332 if (sm[3].matched)
333 format = FourCCToPixelFormat(sm[3]);
334 }
336 vector<Framebuffer*> v;
338 for (unsigned i = 0; i < s_num_buffers; ++i)
339 v.push_back(new DumbFramebuffer(card, w, h, format));
341 return v;
342 }
344 static void parse_view(const string& view_str, PlaneInfo& pinfo)
345 {
346 const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)"); // 400,400-400x400
348 smatch sm;
349 if (!regex_match(view_str, sm, view_re))
350 EXIT("Failed to parse view option '%s'", view_str.c_str());
352 pinfo.view_x = stoul(sm[1]);
353 pinfo.view_y = stoul(sm[2]);
354 pinfo.view_w = stoul(sm[3]);
355 pinfo.view_h = stoul(sm[4]);
356 }
358 static const char* usage_str =
359 "Usage: kmstest [OPTION]...\n\n"
360 "Show a test pattern on a display or plane\n\n"
361 "Options:\n"
362 " --device=DEVICE DEVICE is the path to DRM card to open\n"
363 " -c, --connector=CONN CONN is <connector>\n"
364 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
365 " or\n"
366 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
367 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
368 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
369 " -v, --view=VIEW VIEW is <x>,<y>-<w>x<h>\n"
370 " -P, --property=PROP=VAL Set PROP to VAL in the previous DRM object\n"
371 " --dmt Search for the given mode from DMT tables\n"
372 " --cea Search for the given mode from CEA tables\n"
373 " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
374 " --flip[=max] Do page flipping for each output with an optional maximum flips count\n"
375 " --sync Synchronize page flipping\n"
376 "\n"
377 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n"
378 "<connector> can also be given by name.\n"
379 "\n"
380 "Options can be given multiple times to set up multiple displays or planes.\n"
381 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
382 "an earlier option.\n"
383 "If you omit parameters, kmstest tries to guess what you mean\n"
384 "\n"
385 "Examples:\n"
386 "\n"
387 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
388 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
389 "XR24 framebuffer on first connected connector in the default mode:\n"
390 " kmstest -f XR24\n\n"
391 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
392 " kmstest -p 400x400 -f XR24\n\n"
393 "Test pattern on the second connector with default mode:\n"
394 " kmstest -c 1\n"
395 "\n"
396 "Environmental variables:\n"
397 " KMSXX_DISABLE_UNIVERSAL_PLANES Don't enable universal planes even if available\n"
398 " KMSXX_DISABLE_ATOMIC Don't enable atomic modesetting even if available\n"
399 ;
401 static void usage()
402 {
403 puts(usage_str);
404 }
406 enum class ArgType
407 {
408 Connector,
409 Crtc,
410 Plane,
411 Framebuffer,
412 View,
413 Property,
414 };
416 struct Arg
417 {
418 ArgType type;
419 string arg;
420 };
422 static string s_device_path = "/dev/dri/card0";
424 static vector<Arg> parse_cmdline(int argc, char **argv)
425 {
426 vector<Arg> args;
428 OptionSet optionset = {
429 Option("|device=",
430 [&](string s)
431 {
432 s_device_path = s;
433 }),
434 Option("c|connector=",
435 [&](string s)
436 {
437 args.push_back(Arg { ArgType::Connector, s });
438 }),
439 Option("r|crtc=", [&](string s)
440 {
441 args.push_back(Arg { ArgType::Crtc, s });
442 }),
443 Option("p|plane=", [&](string s)
444 {
445 args.push_back(Arg { ArgType::Plane, s });
446 }),
447 Option("f|fb=", [&](string s)
448 {
449 args.push_back(Arg { ArgType::Framebuffer, s });
450 }),
451 Option("v|view=", [&](string s)
452 {
453 args.push_back(Arg { ArgType::View, s });
454 }),
455 Option("P|property=", [&](string s)
456 {
457 args.push_back(Arg { ArgType::Property, s });
458 }),
459 Option("|dmt", []()
460 {
461 s_use_dmt = true;
462 }),
463 Option("|cea", []()
464 {
465 s_use_cea = true;
466 }),
467 Option("|flip?", [&](string s)
468 {
469 s_flip_mode = true;
470 s_num_buffers = 2;
471 if (!s.empty())
472 s_max_flips = stoi(s);
473 }),
474 Option("|sync", []()
475 {
476 s_flip_sync = true;
477 }),
478 Option("|cvt=", [&](string s)
479 {
480 if (s == "v1")
481 s_cvt = true;
482 else if (s == "v2")
483 s_cvt = s_cvt_v2 = true;
484 else if (s == "v2o")
485 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
486 else {
487 usage();
488 exit(-1);
489 }
490 }),
491 Option("h|help", [&]()
492 {
493 usage();
494 exit(-1);
495 }),
496 };
498 optionset.parse(argc, argv);
500 if (optionset.params().size() > 0) {
501 usage();
502 exit(-1);
503 }
505 return args;
506 }
508 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
509 {
510 vector<OutputInfo> outputs;
512 if (output_args.size() == 0) {
513 // no output args, show a pattern on all screens
514 for (Connector* conn : card.get_connectors()) {
515 if (!conn->connected())
516 continue;
518 OutputInfo output = { };
519 output.connector = resman.reserve_connector(conn);
520 EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str());
521 output.crtc = resman.reserve_crtc(conn);
522 EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str());
523 output.mode = output.connector->get_default_mode();
525 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
527 outputs.push_back(output);
528 }
530 return outputs;
531 }
533 OutputInfo* current_output = 0;
534 PlaneInfo* current_plane = 0;
536 for (auto& arg : output_args) {
537 switch (arg.type) {
538 case ArgType::Connector:
539 {
540 outputs.push_back(OutputInfo { });
541 current_output = &outputs.back();
543 get_connector(resman, *current_output, arg.arg);
544 current_plane = 0;
546 break;
547 }
549 case ArgType::Crtc:
550 {
551 if (!current_output) {
552 outputs.push_back(OutputInfo { });
553 current_output = &outputs.back();
554 }
556 if (!current_output->connector)
557 get_connector(resman, *current_output);
559 parse_crtc(resman, card, arg.arg, *current_output);
561 current_output->user_set_crtc = true;
563 current_plane = 0;
565 break;
566 }
568 case ArgType::Plane:
569 {
570 if (!current_output) {
571 outputs.push_back(OutputInfo { });
572 current_output = &outputs.back();
573 }
575 if (!current_output->connector)
576 get_connector(resman, *current_output);
578 if (!current_output->crtc)
579 get_default_crtc(resman, *current_output);
581 current_output->planes.push_back(PlaneInfo { });
582 current_plane = ¤t_output->planes.back();
584 parse_plane(resman, card, arg.arg, *current_output, *current_plane);
586 break;
587 }
589 case ArgType::Framebuffer:
590 {
591 if (!current_output) {
592 outputs.push_back(OutputInfo { });
593 current_output = &outputs.back();
594 }
596 if (!current_output->connector)
597 get_connector(resman, *current_output);
599 if (!current_output->crtc)
600 get_default_crtc(resman, *current_output);
602 int def_w, def_h;
604 if (current_plane) {
605 def_w = current_plane->w;
606 def_h = current_plane->h;
607 } else {
608 def_w = current_output->mode.hdisplay;
609 def_h = current_output->mode.vdisplay;
610 }
612 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
614 if (current_plane)
615 current_plane->fbs = fbs;
616 else
617 current_output->fbs = fbs;
619 break;
620 }
622 case ArgType::View:
623 {
624 if (!current_plane || current_plane->fbs.empty())
625 EXIT("'view' parameter requires a plane and a fb");
627 parse_view(arg.arg, *current_plane);
628 break;
629 }
631 case ArgType::Property:
632 {
633 if (!current_output)
634 EXIT("No object to which set the property");
636 if (current_plane)
637 parse_prop(card, arg.arg, current_plane->props,
638 current_plane->plane);
639 else if (current_output->crtc)
640 parse_prop(card, arg.arg,
641 current_output->crtc_props,
642 current_output->crtc);
643 else if (current_output->connector)
644 parse_prop(card, arg.arg,
645 current_output->conn_props,
646 current_output->connector);
647 else
648 EXIT("no object");
650 break;
651 }
652 }
653 }
655 // create default framebuffers if needed
656 for (OutputInfo& o : outputs) {
657 if (!o.crtc) {
658 get_default_crtc(resman, o);
659 o.user_set_crtc = true;
660 }
662 if (o.fbs.empty() && o.user_set_crtc)
663 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
665 for (PlaneInfo &p : o.planes) {
666 if (p.fbs.empty())
667 p.fbs = get_default_fb(card, p.w, p.h);
668 }
669 }
671 return outputs;
672 }
674 static std::string videomode_to_string(const Videomode& m)
675 {
676 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
677 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
679 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
680 m.name.c_str(),
681 m.clock / 1000.0,
682 h.c_str(), v.c_str(),
683 m.vrefresh, m.calculated_vrefresh(),
684 m.flags,
685 m.type);
686 }
688 static void print_outputs(const vector<OutputInfo>& outputs)
689 {
690 for (unsigned i = 0; i < outputs.size(); ++i) {
691 const OutputInfo& o = outputs[i];
693 printf("Connector %u/@%u: %s", o.connector->idx(), o.connector->id(),
694 o.connector->fullname().c_str());
696 for (const PropInfo &prop: o.conn_props)
697 printf(" %s=%" PRIu64, prop.prop->name().c_str(),
698 prop.val);
700 printf("\n Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
702 for (const PropInfo &prop: o.crtc_props)
703 printf(" %s=%" PRIu64, prop.prop->name().c_str(),
704 prop.val);
706 if (o.primary_plane)
707 printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
708 printf(": %s\n", videomode_to_string(o.mode).c_str());
709 if (!o.fbs.empty()) {
710 auto fb = o.fbs[0];
711 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
712 PixelFormatToFourCC(fb->format()).c_str());
713 }
715 for (unsigned j = 0; j < o.planes.size(); ++j) {
716 const PlaneInfo& p = o.planes[j];
717 auto fb = p.fbs[0];
718 printf(" Plane %u/@%u: %u,%u-%ux%u", p.plane->idx(), p.plane->id(),
719 p.x, p.y, p.w, p.h);
720 for (const PropInfo &prop: p.props)
721 printf(" %s=%" PRIu64, prop.prop->name().c_str(),
722 prop.val);
723 printf("\n");
725 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
726 PixelFormatToFourCC(fb->format()).c_str());
727 }
728 }
729 }
731 static void draw_test_patterns(const vector<OutputInfo>& outputs)
732 {
733 for (const OutputInfo& o : outputs) {
734 for (auto fb : o.fbs)
735 draw_test_pattern(*fb);
737 for (const PlaneInfo& p : o.planes)
738 for (auto fb : p.fbs)
739 draw_test_pattern(*fb);
740 }
741 }
743 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
744 {
745 // Disable unused crtcs
746 for (Crtc* crtc : card.get_crtcs()) {
747 if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
748 continue;
750 crtc->disable_mode();
751 }
753 for (const OutputInfo& o : outputs) {
754 auto conn = o.connector;
755 auto crtc = o.crtc;
757 if (!o.conn_props.empty() || !o.crtc_props.empty())
758 printf("WARNING: properties not set without atomic modesetting");
760 if (!o.fbs.empty()) {
761 auto fb = o.fbs[0];
762 int r = crtc->set_mode(conn, *fb, o.mode);
763 if (r)
764 printf("crtc->set_mode() failed for crtc %u: %s\n",
765 crtc->id(), strerror(-r));
766 }
768 for (const PlaneInfo& p : o.planes) {
769 auto fb = p.fbs[0];
770 int r = crtc->set_plane(p.plane, *fb,
771 p.x, p.y, p.w, p.h,
772 0, 0, fb->width(), fb->height());
773 if (r)
774 printf("crtc->set_plane() failed for plane %u: %s\n",
775 p.plane->id(), strerror(-r));
776 if (!p.props.empty())
777 printf("WARNING: properties not set without atomic modesetting");
778 }
779 }
780 }
782 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
783 {
784 int r;
786 // XXX DRM framework doesn't allow moving an active plane from one crtc to another.
787 // See drm_atomic.c::plane_switching_crtc().
788 // For the time being, disable all crtcs and planes here.
790 AtomicReq disable_req(card);
792 // Disable unused crtcs
793 for (Crtc* crtc : card.get_crtcs()) {
794 //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
795 // continue;
797 disable_req.add(crtc, {
798 { "ACTIVE", 0 },
799 });
800 }
802 // Disable unused planes
803 for (Plane* plane : card.get_planes()) {
804 //if (find_if(outputs.begin(), outputs.end(), [plane](const OutputInfo& o) { return o.primary_plane == plane; }) != outputs.end())
805 // continue;
807 disable_req.add(plane, {
808 { "FB_ID", 0 },
809 { "CRTC_ID", 0 },
810 });
811 }
813 r = disable_req.commit_sync(true);
814 if (r)
815 EXIT("Atomic commit failed when disabling: %d\n", r);
818 // Keep blobs here so that we keep ref to them until we have committed the req
819 vector<unique_ptr<Blob>> blobs;
821 AtomicReq req(card);
823 for (const OutputInfo& o : outputs) {
824 auto conn = o.connector;
825 auto crtc = o.crtc;
827 blobs.emplace_back(o.mode.to_blob(card));
828 Blob* mode_blob = blobs.back().get();
830 req.add(conn, {
831 { "CRTC_ID", crtc->id() },
832 });
834 for (const PropInfo &prop: o.conn_props)
835 req.add(conn, prop.prop, prop.val);
837 req.add(crtc, {
838 { "ACTIVE", 1 },
839 { "MODE_ID", mode_blob->id() },
840 });
842 for (const PropInfo &prop: o.crtc_props)
843 req.add(crtc, prop.prop, prop.val);
845 if (!o.fbs.empty()) {
846 auto fb = o.fbs[0];
848 req.add(o.primary_plane, {
849 { "FB_ID", fb->id() },
850 { "CRTC_ID", crtc->id() },
851 { "SRC_X", 0 << 16 },
852 { "SRC_Y", 0 << 16 },
853 { "SRC_W", fb->width() << 16 },
854 { "SRC_H", fb->height() << 16 },
855 { "CRTC_X", 0 },
856 { "CRTC_Y", 0 },
857 { "CRTC_W", fb->width() },
858 { "CRTC_H", fb->height() },
859 });
860 }
862 for (const PlaneInfo& p : o.planes) {
863 auto fb = p.fbs[0];
865 req.add(p.plane, {
866 { "FB_ID", fb->id() },
867 { "CRTC_ID", crtc->id() },
868 { "SRC_X", (p.view_x ?: 0) << 16 },
869 { "SRC_Y", (p.view_y ?: 0) << 16 },
870 { "SRC_W", (p.view_w ?: fb->width()) << 16 },
871 { "SRC_H", (p.view_h ?: fb->height()) << 16 },
872 { "CRTC_X", p.x },
873 { "CRTC_Y", p.y },
874 { "CRTC_W", p.w },
875 { "CRTC_H", p.h },
876 });
878 for (const PropInfo &prop: p.props)
879 req.add(p.plane, prop.prop, prop.val);
880 }
881 }
883 r = req.test(true);
884 if (r)
885 EXIT("Atomic test failed: %d\n", r);
887 r = req.commit_sync(true);
888 if (r)
889 EXIT("Atomic commit failed: %d\n", r);
890 }
892 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
893 {
894 if (card.has_atomic())
895 set_crtcs_n_planes_atomic(card, outputs);
896 else
897 set_crtcs_n_planes_legacy(card, outputs);
898 }
900 static bool max_flips_reached;
902 class FlipState : private PageFlipHandlerBase
903 {
904 public:
905 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
906 : m_card(card), m_name(name), m_outputs(outputs)
907 {
908 }
910 void start_flipping()
911 {
912 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
913 m_slowest_frame = std::chrono::duration<float>::min();
914 m_frame_num = 0;
915 queue_next();
916 }
918 private:
919 void handle_page_flip(uint32_t frame, double time)
920 {
921 /*
922 * We get flip event for each crtc in this flipstate. We can commit the next frames
923 * only after we've gotten the flip event for all crtcs
924 */
925 if (++m_flip_count < m_outputs.size())
926 return;
928 m_frame_num++;
929 if (s_max_flips && m_frame_num >= s_max_flips)
930 max_flips_reached = true;
932 auto now = std::chrono::steady_clock::now();
934 std::chrono::duration<float> diff = now - m_prev_frame;
935 if (diff > m_slowest_frame)
936 m_slowest_frame = diff;
938 if (m_frame_num % 100 == 0) {
939 std::chrono::duration<float> fsec = now - m_prev_print;
940 printf("Connector %s: fps %f, slowest %.2f ms\n",
941 m_name.c_str(),
942 100.0 / fsec.count(),
943 m_slowest_frame.count() * 1000);
944 m_prev_print = now;
945 m_slowest_frame = std::chrono::duration<float>::min();
946 }
948 m_prev_frame = now;
950 queue_next();
951 }
953 static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num)
954 {
955 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
956 }
958 static void draw_bar(Framebuffer* fb, unsigned frame_num)
959 {
960 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
961 int new_xpos = get_bar_pos(fb, frame_num);
963 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
964 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
965 }
967 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
968 {
969 unsigned cur = frame_num % s_num_buffers;
971 if (!o.fbs.empty()) {
972 auto fb = o.fbs[cur];
974 draw_bar(fb, frame_num);
976 req.add(o.primary_plane, {
977 { "FB_ID", fb->id() },
978 });
979 }
981 for (const PlaneInfo& p : o.planes) {
982 auto fb = p.fbs[cur];
984 draw_bar(fb, frame_num);
986 req.add(p.plane, {
987 { "FB_ID", fb->id() },
988 });
989 }
990 }
992 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
993 {
994 unsigned cur = frame_num % s_num_buffers;
996 if (!o.fbs.empty()) {
997 auto fb = o.fbs[cur];
999 draw_bar(fb, frame_num);
1001 int r = o.crtc->page_flip(*fb, this);
1002 ASSERT(r == 0);
1003 }
1005 for (const PlaneInfo& p : o.planes) {
1006 auto fb = p.fbs[cur];
1008 draw_bar(fb, frame_num);
1010 int r = o.crtc->set_plane(p.plane, *fb,
1011 p.x, p.y, p.w, p.h,
1012 0, 0, fb->width(), fb->height());
1013 ASSERT(r == 0);
1014 }
1015 }
1017 void queue_next()
1018 {
1019 m_flip_count = 0;
1021 if (m_card.has_atomic()) {
1022 AtomicReq req(m_card);
1024 for (auto o : m_outputs)
1025 do_flip_output(req, m_frame_num, *o);
1027 int r = req.commit(this);
1028 if (r)
1029 EXIT("Flip commit failed: %d\n", r);
1030 } else {
1031 ASSERT(m_outputs.size() == 1);
1032 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
1033 }
1034 }
1036 Card& m_card;
1037 string m_name;
1038 vector<const OutputInfo*> m_outputs;
1039 unsigned m_frame_num;
1040 unsigned m_flip_count;
1042 chrono::steady_clock::time_point m_prev_print;
1043 chrono::steady_clock::time_point m_prev_frame;
1044 chrono::duration<float> m_slowest_frame;
1046 static const unsigned bar_width = 20;
1047 static const unsigned bar_speed = 8;
1048 };
1050 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
1051 {
1052 fd_set fds;
1054 FD_ZERO(&fds);
1056 int fd = card.fd();
1058 vector<unique_ptr<FlipState>> flipstates;
1060 if (!s_flip_sync) {
1061 for (const OutputInfo& o : outputs) {
1062 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
1063 flipstates.push_back(move(fs));
1064 }
1065 } else {
1066 vector<const OutputInfo*> ois;
1068 string name;
1069 for (const OutputInfo& o : outputs) {
1070 name += to_string(o.connector->idx()) + ",";
1071 ois.push_back(&o);
1072 }
1074 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1075 flipstates.push_back(move(fs));
1076 }
1078 for (unique_ptr<FlipState>& fs : flipstates)
1079 fs->start_flipping();
1081 while (!max_flips_reached) {
1082 int r;
1084 FD_SET(0, &fds);
1085 FD_SET(fd, &fds);
1087 r = select(fd + 1, &fds, NULL, NULL, NULL);
1088 if (r < 0) {
1089 fprintf(stderr, "select() failed with %d: %m\n", errno);
1090 break;
1091 } else if (FD_ISSET(0, &fds)) {
1092 fprintf(stderr, "Exit due to user-input\n");
1093 break;
1094 } else if (FD_ISSET(fd, &fds)) {
1095 card.call_page_flip_handlers();
1096 }
1097 }
1098 }
1100 int main(int argc, char **argv)
1101 {
1102 vector<Arg> output_args = parse_cmdline(argc, argv);
1104 Card card(s_device_path);
1106 if (!card.has_atomic() && s_flip_sync)
1107 EXIT("Synchronized flipping requires atomic modesetting");
1109 ResourceManager resman(card);
1111 vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1113 if (card.has_atomic()) {
1114 for (OutputInfo& o : outputs) {
1115 if (o.fbs.empty())
1116 continue;
1118 o.primary_plane = resman.reserve_primary_plane(o.crtc, o.fbs[0]->format());
1120 if (!o.primary_plane)
1121 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
1122 }
1123 }
1125 if (!s_flip_mode)
1126 draw_test_patterns(outputs);
1128 print_outputs(outputs);
1130 set_crtcs_n_planes(card, outputs);
1132 printf("press enter to exit\n");
1134 if (s_flip_mode)
1135 main_flip(card, outputs);
1136 else
1137 getchar();
1138 }