63b7d44df51a3e92f9cc27c437bf555cc3b0d5fb
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
8 #include <sys/select.h>
10 #include <kms++/kms++.h>
11 #include <kms++/modedb.h>
12 #include <kms++/mode_cvt.h>
14 #include <kms++util/kms++util.h>
16 using namespace std;
17 using namespace kms;
19 struct PlaneInfo
20 {
21 Plane* plane;
23 unsigned x;
24 unsigned y;
25 unsigned w;
26 unsigned h;
28 unsigned view_x;
29 unsigned view_y;
30 unsigned view_w;
31 unsigned view_h;
33 vector<Framebuffer*> fbs;
34 };
36 struct OutputInfo
37 {
38 Connector* connector;
40 Crtc* crtc;
41 Plane* primary_plane;
42 Videomode mode;
43 bool user_set_crtc;
44 vector<Framebuffer*> fbs;
46 vector<PlaneInfo> planes;
47 };
49 static bool s_use_dmt;
50 static bool s_use_cea;
51 static unsigned s_num_buffers = 1;
52 static bool s_flip_mode;
53 static bool s_flip_sync;
54 static bool s_cvt;
55 static bool s_cvt_v2;
56 static bool s_cvt_vid_opt;
57 static unsigned s_max_flips;
59 static set<Crtc*> s_used_crtcs;
60 static set<Plane*> s_used_planes;
62 __attribute__ ((unused))
63 static void print_regex_match(smatch sm)
64 {
65 for (unsigned i = 0; i < sm.size(); ++i) {
66 string str = sm[i].str();
67 printf("%u: %s\n", i, str.c_str());
68 }
69 }
71 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
72 {
73 Connector* conn = resman.reserve_connector(str);
75 if (!conn)
76 EXIT("No connector '%s'", str.c_str());
78 if (!conn->connected())
79 EXIT("Connector '%s' not connected", conn->fullname().c_str());
81 output.connector = conn;
82 output.mode = output.connector->get_default_mode();
83 }
85 static void get_default_crtc(Card& card, OutputInfo& output)
86 {
87 Crtc* crtc = output.connector->get_current_crtc();
89 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
90 s_used_crtcs.insert(crtc);
91 output.crtc = crtc;
92 return;
93 }
95 for (const auto& possible : output.connector->get_possible_crtcs()) {
96 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
97 s_used_crtcs.insert(possible);
98 output.crtc = possible;
99 return;
100 }
101 }
103 EXIT("Could not find available crtc");
104 }
106 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
107 {
108 // @12:1920x1200i@60
109 // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
111 const regex modename_re("(?:(@?)(\\d+):)?" // @12:
112 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
113 "(?:@([\\d\\.]+))?"); // @60
115 const regex modeline_re("(?:(@?)(\\d+):)?" // @12:
116 "(\\d+)," // 33000000,
117 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])," // 800/210/30/16/-,
118 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])" // 480/22/13/10/-
119 "(?:,([i]+))?" // ,i
120 );
122 smatch sm;
123 if (regex_match(crtc_str, sm, modename_re)) {
124 if (sm[2].matched) {
125 bool use_id = sm[1].length() == 1;
126 unsigned num = stoul(sm[2].str());
128 if (use_id) {
129 Crtc* c = card.get_crtc(num);
130 if (!c)
131 EXIT("Bad crtc id '%u'", num);
133 output.crtc = c;
134 } else {
135 auto crtcs = card.get_crtcs();
137 if (num >= crtcs.size())
138 EXIT("Bad crtc number '%u'", num);
140 output.crtc = crtcs[num];
141 }
142 } else {
143 output.crtc = output.connector->get_current_crtc();
144 }
146 unsigned w = stoul(sm[3]);
147 unsigned h = stoul(sm[4]);
148 bool ilace = sm[5].matched ? true : false;
149 float refresh = sm[6].matched ? stof(sm[6]) : 0;
151 if (s_cvt) {
152 output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
153 } else if (s_use_dmt) {
154 try {
155 output.mode = find_dmt(w, h, refresh, ilace);
156 } catch (exception& e) {
157 EXIT("Mode not found from DMT tables\n");
158 }
159 } else if (s_use_cea) {
160 try {
161 output.mode = find_cea(w, h, refresh, ilace);
162 } catch (exception& e) {
163 EXIT("Mode not found from CEA tables\n");
164 }
165 } else {
166 try {
167 output.mode = output.connector->get_mode(w, h, refresh, ilace);
168 } catch (exception& e) {
169 EXIT("Mode not found from the connector\n");
170 }
171 }
172 } else if (regex_match(crtc_str, sm, modeline_re)) {
173 if (sm[2].matched) {
174 bool use_id = sm[1].length() == 1;
175 unsigned num = stoul(sm[2].str());
177 if (use_id) {
178 Crtc* c = card.get_crtc(num);
179 if (!c)
180 EXIT("Bad crtc id '%u'", num);
182 output.crtc = c;
183 } else {
184 auto crtcs = card.get_crtcs();
186 if (num >= crtcs.size())
187 EXIT("Bad crtc number '%u'", num);
189 output.crtc = crtcs[num];
190 }
191 } else {
192 output.crtc = output.connector->get_current_crtc();
193 }
195 unsigned clock = stoul(sm[3]);
197 unsigned hact = stoul(sm[4]);
198 unsigned hfp = stoul(sm[5]);
199 unsigned hsw = stoul(sm[6]);
200 unsigned hbp = stoul(sm[7]);
201 bool h_pos_sync = sm[8] == "+" ? true : false;
203 unsigned vact = stoul(sm[9]);
204 unsigned vfp = stoul(sm[10]);
205 unsigned vsw = stoul(sm[11]);
206 unsigned vbp = stoul(sm[12]);
207 bool v_pos_sync = sm[13] == "+" ? true : false;
209 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
210 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
211 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
213 if (sm[14].matched) {
214 for (int i = 0; i < sm[14].length(); ++i) {
215 char f = string(sm[14])[i];
217 switch (f) {
218 case 'i':
219 output.mode.set_interlace(true);
220 break;
221 default:
222 EXIT("Bad mode flag %c", f);
223 }
224 }
225 }
226 } else {
227 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
228 }
229 }
231 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
232 {
233 // 3:400,400-400x400
234 const regex plane_re("(?:(@?)(\\d+):)?" // 3:
235 "(?:(\\d+),(\\d+)-)?" // 400,400-
236 "(\\d+)x(\\d+)"); // 400x400
238 smatch sm;
239 if (!regex_match(plane_str, sm, plane_re))
240 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
242 if (sm[2].matched) {
243 bool use_id = sm[1].length() == 1;
244 unsigned num = stoul(sm[2].str());
246 if (use_id) {
247 Plane* p = card.get_plane(num);
248 if (!p)
249 EXIT("Bad plane id '%u'", num);
251 pinfo.plane = p;
252 } else {
253 auto planes = card.get_planes();
255 if (num >= planes.size())
256 EXIT("Bad plane number '%u'", num);
258 pinfo.plane = planes[num];
259 }
260 } else {
261 for (Plane* p : output.crtc->get_possible_planes()) {
262 if (s_used_planes.find(p) != s_used_planes.end())
263 continue;
265 if (p->plane_type() != PlaneType::Overlay)
266 continue;
268 pinfo.plane = p;
269 }
271 if (!pinfo.plane)
272 EXIT("Failed to find available plane");
273 }
275 s_used_planes.insert(pinfo.plane);
277 pinfo.w = stoul(sm[5]);
278 pinfo.h = stoul(sm[6]);
280 if (sm[3].matched)
281 pinfo.x = stoul(sm[3]);
282 else
283 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
285 if (sm[4].matched)
286 pinfo.y = stoul(sm[4]);
287 else
288 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
289 }
291 static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
292 {
293 vector<Framebuffer*> v;
295 for (unsigned i = 0; i < s_num_buffers; ++i)
296 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
298 return v;
299 }
301 static vector<Framebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
302 {
303 unsigned w = def_w;
304 unsigned h = def_h;
305 PixelFormat format = PixelFormat::XRGB8888;
307 if (!fb_str.empty()) {
308 // XXX the regexp is not quite correct
309 // 400x400-NV12
310 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400
311 "(?:-)?" // -
312 "(\\w\\w\\w\\w)?"); // NV12
314 smatch sm;
315 if (!regex_match(fb_str, sm, fb_re))
316 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
318 if (sm[1].matched)
319 w = stoul(sm[1]);
320 if (sm[2].matched)
321 h = stoul(sm[2]);
322 if (sm[3].matched)
323 format = FourCCToPixelFormat(sm[3]);
324 }
326 vector<Framebuffer*> v;
328 for (unsigned i = 0; i < s_num_buffers; ++i)
329 v.push_back(new DumbFramebuffer(card, w, h, format));
331 return v;
332 }
334 static void parse_view(const string& view_str, PlaneInfo& pinfo)
335 {
336 const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)"); // 400,400-400x400
338 smatch sm;
339 if (!regex_match(view_str, sm, view_re))
340 EXIT("Failed to parse view option '%s'", view_str.c_str());
342 pinfo.view_x = stoul(sm[1]);
343 pinfo.view_y = stoul(sm[2]);
344 pinfo.view_w = stoul(sm[3]);
345 pinfo.view_h = stoul(sm[4]);
346 }
348 static const char* usage_str =
349 "Usage: kmstest [OPTION]...\n\n"
350 "Show a test pattern on a display or plane\n\n"
351 "Options:\n"
352 " --device=DEVICE DEVICE is the path to DRM card to open\n"
353 " -c, --connector=CONN CONN is <connector>\n"
354 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
355 " or\n"
356 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
357 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
358 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
359 " -v, --view=VIEW VIEW is <x>,<y>-<w>x<h>\n"
360 " --dmt Search for the given mode from DMT tables\n"
361 " --cea Search for the given mode from CEA tables\n"
362 " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
363 " --flip[=max] Do page flipping for each output with an optional maximum flips count\n"
364 " --sync Synchronize page flipping\n"
365 "\n"
366 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n"
367 "<connector> can also be given by name.\n"
368 "\n"
369 "Options can be given multiple times to set up multiple displays or planes.\n"
370 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
371 "an earlier option.\n"
372 "If you omit parameters, kmstest tries to guess what you mean\n"
373 "\n"
374 "Examples:\n"
375 "\n"
376 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
377 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
378 "XR24 framebuffer on first connected connector in the default mode:\n"
379 " kmstest -f XR24\n\n"
380 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
381 " kmstest -p 400x400 -f XR24\n\n"
382 "Test pattern on the second connector with default mode:\n"
383 " kmstest -c 1\n"
384 "\n"
385 "Environmental variables:\n"
386 " KMSXX_DISABLE_UNIVERSAL_PLANES Don't enable universal planes even if available\n"
387 " KMSXX_DISABLE_ATOMIC Don't enable atomic modesetting even if available\n"
388 ;
390 static void usage()
391 {
392 puts(usage_str);
393 }
395 enum class ObjectType
396 {
397 Connector,
398 Crtc,
399 Plane,
400 Framebuffer,
401 View,
402 };
404 struct Arg
405 {
406 ObjectType type;
407 string arg;
408 };
410 static string s_device_path = "/dev/dri/card0";
412 static vector<Arg> parse_cmdline(int argc, char **argv)
413 {
414 vector<Arg> args;
416 OptionSet optionset = {
417 Option("|device=",
418 [&](string s)
419 {
420 s_device_path = s;
421 }),
422 Option("c|connector=",
423 [&](string s)
424 {
425 args.push_back(Arg { ObjectType::Connector, s });
426 }),
427 Option("r|crtc=", [&](string s)
428 {
429 args.push_back(Arg { ObjectType::Crtc, s });
430 }),
431 Option("p|plane=", [&](string s)
432 {
433 args.push_back(Arg { ObjectType::Plane, s });
434 }),
435 Option("f|fb=", [&](string s)
436 {
437 args.push_back(Arg { ObjectType::Framebuffer, s });
438 }),
439 Option("v|view=", [&](string s)
440 {
441 args.push_back(Arg { ObjectType::View, s });
442 }),
443 Option("|dmt", []()
444 {
445 s_use_dmt = true;
446 }),
447 Option("|cea", []()
448 {
449 s_use_cea = true;
450 }),
451 Option("|flip?", [&](string s)
452 {
453 s_flip_mode = true;
454 s_num_buffers = 2;
455 if (!s.empty())
456 s_max_flips = stoi(s);
457 }),
458 Option("|sync", []()
459 {
460 s_flip_sync = true;
461 }),
462 Option("|cvt=", [&](string s)
463 {
464 if (s == "v1")
465 s_cvt = true;
466 else if (s == "v2")
467 s_cvt = s_cvt_v2 = true;
468 else if (s == "v2o")
469 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
470 else {
471 usage();
472 exit(-1);
473 }
474 }),
475 Option("h|help", [&]()
476 {
477 usage();
478 exit(-1);
479 }),
480 };
482 optionset.parse(argc, argv);
484 if (optionset.params().size() > 0) {
485 usage();
486 exit(-1);
487 }
489 return args;
490 }
492 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
493 {
494 vector<OutputInfo> outputs;
496 if (output_args.size() == 0) {
497 // no output args, show a pattern on all screens
498 for (Connector* conn : card.get_connectors()) {
499 if (!conn->connected())
500 continue;
502 OutputInfo output = { };
503 output.connector = resman.reserve_connector(conn);
504 EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str());
505 output.crtc = resman.reserve_crtc(conn);
506 EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str());
507 output.mode = output.connector->get_default_mode();
509 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
511 outputs.push_back(output);
512 }
514 return outputs;
515 }
517 OutputInfo* current_output = 0;
518 PlaneInfo* current_plane = 0;
520 for (auto& arg : output_args) {
521 switch (arg.type) {
522 case ObjectType::Connector:
523 {
524 outputs.push_back(OutputInfo { });
525 current_output = &outputs.back();
527 get_connector(resman, *current_output, arg.arg);
528 current_plane = 0;
530 break;
531 }
533 case ObjectType::Crtc:
534 {
535 if (!current_output) {
536 outputs.push_back(OutputInfo { });
537 current_output = &outputs.back();
538 }
540 if (!current_output->connector)
541 get_connector(resman, *current_output);
543 parse_crtc(card, arg.arg, *current_output);
545 current_output->user_set_crtc = true;
547 current_plane = 0;
549 break;
550 }
552 case ObjectType::Plane:
553 {
554 if (!current_output) {
555 outputs.push_back(OutputInfo { });
556 current_output = &outputs.back();
557 }
559 if (!current_output->connector)
560 get_connector(resman, *current_output);
562 if (!current_output->crtc)
563 get_default_crtc(card, *current_output);
565 current_output->planes.push_back(PlaneInfo { });
566 current_plane = ¤t_output->planes.back();
568 parse_plane(card, arg.arg, *current_output, *current_plane);
570 break;
571 }
573 case ObjectType::Framebuffer:
574 {
575 if (!current_output) {
576 outputs.push_back(OutputInfo { });
577 current_output = &outputs.back();
578 }
580 if (!current_output->connector)
581 get_connector(resman, *current_output);
583 if (!current_output->crtc)
584 get_default_crtc(card, *current_output);
586 int def_w, def_h;
588 if (current_plane) {
589 def_w = current_plane->w;
590 def_h = current_plane->h;
591 } else {
592 def_w = current_output->mode.hdisplay;
593 def_h = current_output->mode.vdisplay;
594 }
596 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
598 if (current_plane)
599 current_plane->fbs = fbs;
600 else
601 current_output->fbs = fbs;
603 break;
604 }
606 case ObjectType::View:
607 {
608 if (!current_plane || current_plane->fbs.empty())
609 EXIT("'view' parameter requires a plane and a fb");
611 parse_view(arg.arg, *current_plane);
612 break;
613 }
614 }
615 }
617 // create default framebuffers if needed
618 for (OutputInfo& o : outputs) {
619 if (!o.crtc) {
620 get_default_crtc(card, o);
621 o.user_set_crtc = true;
622 }
624 if (o.fbs.empty() && o.user_set_crtc)
625 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
627 for (PlaneInfo &p : o.planes) {
628 if (p.fbs.empty())
629 p.fbs = get_default_fb(card, p.w, p.h);
630 }
631 }
633 return outputs;
634 }
636 static std::string videomode_to_string(const Videomode& m)
637 {
638 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
639 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
641 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
642 m.name.c_str(),
643 m.clock / 1000.0,
644 h.c_str(), v.c_str(),
645 m.vrefresh, m.calculated_vrefresh(),
646 m.flags,
647 m.type);
648 }
650 static void print_outputs(const vector<OutputInfo>& outputs)
651 {
652 for (unsigned i = 0; i < outputs.size(); ++i) {
653 const OutputInfo& o = outputs[i];
655 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
656 o.connector->fullname().c_str());
657 printf(" Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
658 if (o.primary_plane)
659 printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
660 printf(": %s\n", videomode_to_string(o.mode).c_str());
661 if (!o.fbs.empty()) {
662 auto fb = o.fbs[0];
663 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
664 PixelFormatToFourCC(fb->format()).c_str());
665 }
667 for (unsigned j = 0; j < o.planes.size(); ++j) {
668 const PlaneInfo& p = o.planes[j];
669 auto fb = p.fbs[0];
670 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
671 p.x, p.y, p.w, p.h);
672 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
673 PixelFormatToFourCC(fb->format()).c_str());
674 }
675 }
676 }
678 static void draw_test_patterns(const vector<OutputInfo>& outputs)
679 {
680 for (const OutputInfo& o : outputs) {
681 for (auto fb : o.fbs)
682 draw_test_pattern(*fb);
684 for (const PlaneInfo& p : o.planes)
685 for (auto fb : p.fbs)
686 draw_test_pattern(*fb);
687 }
688 }
690 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
691 {
692 // Disable unused crtcs
693 for (Crtc* crtc : card.get_crtcs()) {
694 if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
695 continue;
697 crtc->disable_mode();
698 }
700 for (const OutputInfo& o : outputs) {
701 auto conn = o.connector;
702 auto crtc = o.crtc;
704 if (!o.fbs.empty()) {
705 auto fb = o.fbs[0];
706 int r = crtc->set_mode(conn, *fb, o.mode);
707 if (r)
708 printf("crtc->set_mode() failed for crtc %u: %s\n",
709 crtc->id(), strerror(-r));
710 }
712 for (const PlaneInfo& p : o.planes) {
713 auto fb = p.fbs[0];
714 int r = crtc->set_plane(p.plane, *fb,
715 p.x, p.y, p.w, p.h,
716 0, 0, fb->width(), fb->height());
717 if (r)
718 printf("crtc->set_plane() failed for plane %u: %s\n",
719 p.plane->id(), strerror(-r));
720 }
721 }
722 }
724 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
725 {
726 int r;
728 // XXX DRM framework doesn't allow moving an active plane from one crtc to another.
729 // See drm_atomic.c::plane_switching_crtc().
730 // For the time being, disable all crtcs and planes here.
732 AtomicReq disable_req(card);
734 // Disable unused crtcs
735 for (Crtc* crtc : card.get_crtcs()) {
736 //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
737 // continue;
739 disable_req.add(crtc, {
740 { "ACTIVE", 0 },
741 });
742 }
744 // Disable unused planes
745 for (Plane* plane : card.get_planes()) {
746 //if (find_if(outputs.begin(), outputs.end(), [plane](const OutputInfo& o) { return o.primary_plane == plane; }) != outputs.end())
747 // continue;
749 disable_req.add(plane, {
750 { "FB_ID", 0 },
751 { "CRTC_ID", 0 },
752 });
753 }
755 r = disable_req.commit_sync(true);
756 if (r)
757 EXIT("Atomic commit failed when disabling: %d\n", r);
760 // Keep blobs here so that we keep ref to them until we have committed the req
761 vector<unique_ptr<Blob>> blobs;
763 AtomicReq req(card);
765 for (const OutputInfo& o : outputs) {
766 auto conn = o.connector;
767 auto crtc = o.crtc;
769 blobs.emplace_back(o.mode.to_blob(card));
770 Blob* mode_blob = blobs.back().get();
772 req.add(conn, {
773 { "CRTC_ID", crtc->id() },
774 });
776 req.add(crtc, {
777 { "ACTIVE", 1 },
778 { "MODE_ID", mode_blob->id() },
779 });
781 if (!o.fbs.empty()) {
782 auto fb = o.fbs[0];
784 req.add(o.primary_plane, {
785 { "FB_ID", fb->id() },
786 { "CRTC_ID", crtc->id() },
787 { "SRC_X", 0 << 16 },
788 { "SRC_Y", 0 << 16 },
789 { "SRC_W", fb->width() << 16 },
790 { "SRC_H", fb->height() << 16 },
791 { "CRTC_X", 0 },
792 { "CRTC_Y", 0 },
793 { "CRTC_W", fb->width() },
794 { "CRTC_H", fb->height() },
795 });
796 }
798 for (const PlaneInfo& p : o.planes) {
799 auto fb = p.fbs[0];
801 req.add(p.plane, {
802 { "FB_ID", fb->id() },
803 { "CRTC_ID", crtc->id() },
804 { "SRC_X", (p.view_x ?: 0) << 16 },
805 { "SRC_Y", (p.view_y ?: 0) << 16 },
806 { "SRC_W", (p.view_w ?: fb->width()) << 16 },
807 { "SRC_H", (p.view_h ?: fb->height()) << 16 },
808 { "CRTC_X", p.x },
809 { "CRTC_Y", p.y },
810 { "CRTC_W", p.w },
811 { "CRTC_H", p.h },
812 });
813 }
814 }
816 r = req.test(true);
817 if (r)
818 EXIT("Atomic test failed: %d\n", r);
820 r = req.commit_sync(true);
821 if (r)
822 EXIT("Atomic commit failed: %d\n", r);
823 }
825 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
826 {
827 if (card.has_atomic())
828 set_crtcs_n_planes_atomic(card, outputs);
829 else
830 set_crtcs_n_planes_legacy(card, outputs);
831 }
833 static bool max_flips_reached;
835 class FlipState : private PageFlipHandlerBase
836 {
837 public:
838 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
839 : m_card(card), m_name(name), m_outputs(outputs)
840 {
841 }
843 void start_flipping()
844 {
845 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
846 m_slowest_frame = std::chrono::duration<float>::min();
847 m_frame_num = 0;
848 queue_next();
849 }
851 private:
852 void handle_page_flip(uint32_t frame, double time)
853 {
854 /*
855 * We get flip event for each crtc in this flipstate. We can commit the next frames
856 * only after we've gotten the flip event for all crtcs
857 */
858 if (++m_flip_count < m_outputs.size())
859 return;
861 m_frame_num++;
862 if (s_max_flips && m_frame_num >= s_max_flips)
863 max_flips_reached = true;
865 auto now = std::chrono::steady_clock::now();
867 std::chrono::duration<float> diff = now - m_prev_frame;
868 if (diff > m_slowest_frame)
869 m_slowest_frame = diff;
871 if (m_frame_num % 100 == 0) {
872 std::chrono::duration<float> fsec = now - m_prev_print;
873 printf("Connector %s: fps %f, slowest %.2f ms\n",
874 m_name.c_str(),
875 100.0 / fsec.count(),
876 m_slowest_frame.count() * 1000);
877 m_prev_print = now;
878 m_slowest_frame = std::chrono::duration<float>::min();
879 }
881 m_prev_frame = now;
883 queue_next();
884 }
886 static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num)
887 {
888 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
889 }
891 static void draw_bar(Framebuffer* fb, unsigned frame_num)
892 {
893 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
894 int new_xpos = get_bar_pos(fb, frame_num);
896 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
897 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
898 }
900 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
901 {
902 unsigned cur = frame_num % s_num_buffers;
904 if (!o.fbs.empty()) {
905 auto fb = o.fbs[cur];
907 draw_bar(fb, frame_num);
909 req.add(o.primary_plane, {
910 { "FB_ID", fb->id() },
911 });
912 }
914 for (const PlaneInfo& p : o.planes) {
915 auto fb = p.fbs[cur];
917 draw_bar(fb, frame_num);
919 req.add(p.plane, {
920 { "FB_ID", fb->id() },
921 });
922 }
923 }
925 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
926 {
927 unsigned cur = frame_num % s_num_buffers;
929 if (!o.fbs.empty()) {
930 auto fb = o.fbs[cur];
932 draw_bar(fb, frame_num);
934 int r = o.crtc->page_flip(*fb, this);
935 ASSERT(r == 0);
936 }
938 for (const PlaneInfo& p : o.planes) {
939 auto fb = p.fbs[cur];
941 draw_bar(fb, frame_num);
943 int r = o.crtc->set_plane(p.plane, *fb,
944 p.x, p.y, p.w, p.h,
945 0, 0, fb->width(), fb->height());
946 ASSERT(r == 0);
947 }
948 }
950 void queue_next()
951 {
952 m_flip_count = 0;
954 if (m_card.has_atomic()) {
955 AtomicReq req(m_card);
957 for (auto o : m_outputs)
958 do_flip_output(req, m_frame_num, *o);
960 int r = req.commit(this);
961 if (r)
962 EXIT("Flip commit failed: %d\n", r);
963 } else {
964 ASSERT(m_outputs.size() == 1);
965 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
966 }
967 }
969 Card& m_card;
970 string m_name;
971 vector<const OutputInfo*> m_outputs;
972 unsigned m_frame_num;
973 unsigned m_flip_count;
975 chrono::steady_clock::time_point m_prev_print;
976 chrono::steady_clock::time_point m_prev_frame;
977 chrono::duration<float> m_slowest_frame;
979 static const unsigned bar_width = 20;
980 static const unsigned bar_speed = 8;
981 };
983 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
984 {
985 fd_set fds;
987 FD_ZERO(&fds);
989 int fd = card.fd();
991 vector<unique_ptr<FlipState>> flipstates;
993 if (!s_flip_sync) {
994 for (const OutputInfo& o : outputs) {
995 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
996 flipstates.push_back(move(fs));
997 }
998 } else {
999 vector<const OutputInfo*> ois;
1001 string name;
1002 for (const OutputInfo& o : outputs) {
1003 name += to_string(o.connector->idx()) + ",";
1004 ois.push_back(&o);
1005 }
1007 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1008 flipstates.push_back(move(fs));
1009 }
1011 for (unique_ptr<FlipState>& fs : flipstates)
1012 fs->start_flipping();
1014 while (!max_flips_reached) {
1015 int r;
1017 FD_SET(0, &fds);
1018 FD_SET(fd, &fds);
1020 r = select(fd + 1, &fds, NULL, NULL, NULL);
1021 if (r < 0) {
1022 fprintf(stderr, "select() failed with %d: %m\n", errno);
1023 break;
1024 } else if (FD_ISSET(0, &fds)) {
1025 fprintf(stderr, "Exit due to user-input\n");
1026 break;
1027 } else if (FD_ISSET(fd, &fds)) {
1028 card.call_page_flip_handlers();
1029 }
1030 }
1031 }
1033 int main(int argc, char **argv)
1034 {
1035 vector<Arg> output_args = parse_cmdline(argc, argv);
1037 Card card(s_device_path);
1039 if (!card.has_atomic() && s_flip_sync)
1040 EXIT("Synchronized flipping requires atomic modesetting");
1042 ResourceManager resman(card);
1044 vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1046 if (card.has_atomic()) {
1047 for (OutputInfo& o : outputs) {
1048 if (o.fbs.empty())
1049 continue;
1051 o.primary_plane = resman.reserve_primary_plane(o.crtc);
1053 if (!o.primary_plane)
1054 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
1055 }
1056 }
1058 if (!s_flip_mode)
1059 draw_test_patterns(outputs);
1061 print_outputs(outputs);
1063 set_crtcs_n_planes(card, outputs);
1065 printf("press enter to exit\n");
1067 if (s_flip_mode)
1068 main_flip(card, outputs);
1069 else
1070 getchar();
1071 }