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 vector<DumbFramebuffer*> fbs;
29 };
31 struct OutputInfo
32 {
33 Connector* connector;
35 Crtc* crtc;
36 Plane* primary_plane;
37 Videomode mode;
38 bool user_set_crtc;
39 vector<DumbFramebuffer*> fbs;
41 vector<PlaneInfo> planes;
42 };
44 static bool s_use_dmt;
45 static bool s_use_cea;
46 static unsigned s_num_buffers = 1;
47 static bool s_flip_mode;
48 static bool s_flip_sync;
49 static bool s_cvt;
50 static bool s_cvt_v2;
51 static bool s_cvt_vid_opt;
53 static set<Crtc*> s_used_crtcs;
54 static set<Plane*> s_used_planes;
56 __attribute__ ((unused))
57 static void print_regex_match(smatch sm)
58 {
59 for (unsigned i = 0; i < sm.size(); ++i) {
60 string str = sm[i].str();
61 printf("%u: %s\n", i, str.c_str());
62 }
63 }
65 static void get_default_connector(Card& card, OutputInfo& output)
66 {
67 output.connector = card.get_first_connected_connector();
68 output.mode = output.connector->get_default_mode();
69 }
71 static void parse_connector(Card& card, const string& str, OutputInfo& output)
72 {
73 Connector* conn = resolve_connector(card, 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 bool found_mode = false;
153 if (s_cvt) {
154 output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
155 found_mode = true;
156 }
158 if (!found_mode) {
159 try {
160 output.mode = output.connector->get_mode(w, h, refresh, ilace);
161 found_mode = true;
162 } catch (exception& e) { }
163 }
165 if (!found_mode && s_use_dmt) {
166 try {
167 output.mode = find_dmt(w, h, refresh, ilace);
168 found_mode = true;
169 printf("Found mode from DMT\n");
170 } catch (exception& e) { }
171 }
173 if (!found_mode && s_use_cea) {
174 try {
175 output.mode = find_cea(w, h, refresh, ilace);
176 found_mode = true;
177 printf("Found mode from CEA\n");
178 } catch (exception& e) { }
179 }
181 if (!found_mode)
182 throw invalid_argument("Mode not found");
183 } else if (regex_match(crtc_str, sm, modeline_re)) {
184 if (sm[2].matched) {
185 bool use_id = sm[1].length() == 1;
186 unsigned num = stoul(sm[2].str());
188 if (use_id) {
189 Crtc* c = card.get_crtc(num);
190 if (!c)
191 EXIT("Bad crtc id '%u'", num);
193 output.crtc = c;
194 } else {
195 auto crtcs = card.get_crtcs();
197 if (num >= crtcs.size())
198 EXIT("Bad crtc number '%u'", num);
200 output.crtc = crtcs[num];
201 }
202 } else {
203 output.crtc = output.connector->get_current_crtc();
204 }
206 unsigned clock = stoul(sm[3]);
208 unsigned hact = stoul(sm[4]);
209 unsigned hfp = stoul(sm[5]);
210 unsigned hsw = stoul(sm[6]);
211 unsigned hbp = stoul(sm[7]);
212 bool h_pos_sync = sm[8] == "+" ? true : false;
214 unsigned vact = stoul(sm[9]);
215 unsigned vfp = stoul(sm[10]);
216 unsigned vsw = stoul(sm[11]);
217 unsigned vbp = stoul(sm[12]);
218 bool v_pos_sync = sm[13] == "+" ? true : false;
220 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
221 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
222 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
224 if (sm[14].matched) {
225 for (int i = 0; i < sm[14].length(); ++i) {
226 char f = string(sm[14])[i];
228 switch (f) {
229 case 'i':
230 output.mode.set_interlace(true);
231 break;
232 default:
233 EXIT("Bad mode flag %c", f);
234 }
235 }
236 }
237 } else {
238 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
239 }
240 }
242 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
243 {
244 // 3:400,400-400x400
245 const regex plane_re("(?:(@?)(\\d+):)?" // 3:
246 "(?:(\\d+),(\\d+)-)?" // 400,400-
247 "(\\d+)x(\\d+)"); // 400x400
249 smatch sm;
250 if (!regex_match(plane_str, sm, plane_re))
251 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
253 if (sm[2].matched) {
254 bool use_id = sm[1].length() == 1;
255 unsigned num = stoul(sm[2].str());
257 if (use_id) {
258 Plane* p = card.get_plane(num);
259 if (!p)
260 EXIT("Bad plane id '%u'", num);
262 pinfo.plane = p;
263 } else {
264 auto planes = card.get_planes();
266 if (num >= planes.size())
267 EXIT("Bad plane number '%u'", num);
269 pinfo.plane = planes[num];
270 }
271 } else {
272 for (Plane* p : output.crtc->get_possible_planes()) {
273 if (s_used_planes.find(p) != s_used_planes.end())
274 continue;
276 if (p->plane_type() != PlaneType::Overlay)
277 continue;
279 pinfo.plane = p;
280 }
282 if (!pinfo.plane)
283 EXIT("Failed to find available plane");
284 }
286 s_used_planes.insert(pinfo.plane);
288 pinfo.w = stoul(sm[5]);
289 pinfo.h = stoul(sm[6]);
291 if (sm[3].matched)
292 pinfo.x = stoul(sm[3]);
293 else
294 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
296 if (sm[4].matched)
297 pinfo.y = stoul(sm[4]);
298 else
299 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
300 }
302 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
303 {
304 vector<DumbFramebuffer*> v;
306 for (unsigned i = 0; i < s_num_buffers; ++i)
307 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
309 return v;
310 }
312 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
313 {
314 unsigned w = def_w;
315 unsigned h = def_h;
316 PixelFormat format = PixelFormat::XRGB8888;
318 if (!fb_str.empty()) {
319 // XXX the regexp is not quite correct
320 // 400x400-NV12
321 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400
322 "(?:-)?" // -
323 "(\\w\\w\\w\\w)?"); // NV12
325 smatch sm;
326 if (!regex_match(fb_str, sm, fb_re))
327 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
329 if (sm[1].matched)
330 w = stoul(sm[1]);
331 if (sm[2].matched)
332 h = stoul(sm[2]);
333 if (sm[3].matched)
334 format = FourCCToPixelFormat(sm[3]);
335 }
337 vector<DumbFramebuffer*> v;
339 for (unsigned i = 0; i < s_num_buffers; ++i)
340 v.push_back(new DumbFramebuffer(card, w, h, format));
342 return v;
343 }
345 static const char* usage_str =
346 "Usage: kmstest [OPTION]...\n\n"
347 "Show a test pattern on a display or plane\n\n"
348 "Options:\n"
349 " --device=DEVICE DEVICE is the path to DRM card to open\n"
350 " -c, --connector=CONN CONN is <connector>\n"
351 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
352 " or\n"
353 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
354 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
355 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
356 " --dmt Search for the given mode from DMT tables\n"
357 " --cea Search for the given mode from CEA tables\n"
358 " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
359 " --flip Do page flipping for each output\n"
360 " --sync Synchronize page flipping\n"
361 "\n"
362 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
363 "<connector> can also be given by name.\n"
364 "\n"
365 "Options can be given multiple times to set up multiple displays or planes.\n"
366 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
367 "an earlier option.\n"
368 "If you omit parameters, kmstest tries to guess what you mean\n"
369 "\n"
370 "Examples:\n"
371 "\n"
372 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
373 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
374 "XR24 framebuffer on first connected connector in the default mode:\n"
375 " kmstest -f XR24\n\n"
376 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
377 " kmstest -p 400x400 -f XR24\n\n"
378 "Test pattern on the second connector with default mode:\n"
379 " kmstest -c 1\n"
380 ;
382 static void usage()
383 {
384 puts(usage_str);
385 }
387 enum class ObjectType
388 {
389 Connector,
390 Crtc,
391 Plane,
392 Framebuffer,
393 };
395 struct Arg
396 {
397 ObjectType type;
398 string arg;
399 };
401 static string s_device_path = "/dev/dri/card0";
403 static vector<Arg> parse_cmdline(int argc, char **argv)
404 {
405 vector<Arg> args;
407 OptionSet optionset = {
408 Option("|device=",
409 [&](string s)
410 {
411 s_device_path = s;
412 }),
413 Option("c|connector=",
414 [&](string s)
415 {
416 args.push_back(Arg { ObjectType::Connector, s });
417 }),
418 Option("r|crtc=", [&](string s)
419 {
420 args.push_back(Arg { ObjectType::Crtc, s });
421 }),
422 Option("p|plane=", [&](string s)
423 {
424 args.push_back(Arg { ObjectType::Plane, s });
425 }),
426 Option("f|fb=", [&](string s)
427 {
428 args.push_back(Arg { ObjectType::Framebuffer, s });
429 }),
430 Option("|dmt", []()
431 {
432 s_use_dmt = true;
433 }),
434 Option("|cea", []()
435 {
436 s_use_cea = true;
437 }),
438 Option("|flip", []()
439 {
440 s_flip_mode = true;
441 s_num_buffers = 2;
442 }),
443 Option("|sync", []()
444 {
445 s_flip_sync = true;
446 }),
447 Option("|cvt=", [&](string s)
448 {
449 if (s == "v1")
450 s_cvt = true;
451 else if (s == "v2")
452 s_cvt = s_cvt_v2 = true;
453 else if (s == "v2o")
454 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
455 else {
456 usage();
457 exit(-1);
458 }
459 }),
460 Option("h|help", [&]()
461 {
462 usage();
463 exit(-1);
464 }),
465 };
467 optionset.parse(argc, argv);
469 if (optionset.params().size() > 0) {
470 usage();
471 exit(-1);
472 }
474 return args;
475 }
477 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
478 {
479 vector<OutputInfo> outputs;
481 if (output_args.size() == 0) {
482 // no output args, show a pattern on all screens
483 for (auto& pipe : card.get_connected_pipelines()) {
484 OutputInfo output = { };
485 output.connector = pipe.connector;
486 output.crtc = pipe.crtc;
487 output.mode = output.connector->get_default_mode();
489 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
491 outputs.push_back(output);
492 }
494 return outputs;
495 }
497 OutputInfo* current_output = 0;
498 PlaneInfo* current_plane = 0;
500 for (auto& arg : output_args) {
501 switch (arg.type) {
502 case ObjectType::Connector:
503 {
504 outputs.push_back(OutputInfo { });
505 current_output = &outputs.back();
507 parse_connector(card, arg.arg, *current_output);
508 current_plane = 0;
510 break;
511 }
513 case ObjectType::Crtc:
514 {
515 if (!current_output) {
516 outputs.push_back(OutputInfo { });
517 current_output = &outputs.back();
518 }
520 if (!current_output->connector)
521 get_default_connector(card, *current_output);
523 parse_crtc(card, arg.arg, *current_output);
525 current_output->user_set_crtc = true;
527 current_plane = 0;
529 break;
530 }
532 case ObjectType::Plane:
533 {
534 if (!current_output) {
535 outputs.push_back(OutputInfo { });
536 current_output = &outputs.back();
537 }
539 if (!current_output->connector)
540 get_default_connector(card, *current_output);
542 if (!current_output->crtc)
543 get_default_crtc(card, *current_output);
545 current_output->planes.push_back(PlaneInfo { });
546 current_plane = ¤t_output->planes.back();
548 parse_plane(card, arg.arg, *current_output, *current_plane);
550 break;
551 }
553 case ObjectType::Framebuffer:
554 {
555 if (!current_output) {
556 outputs.push_back(OutputInfo { });
557 current_output = &outputs.back();
558 }
560 if (!current_output->connector)
561 get_default_connector(card, *current_output);
563 if (!current_output->crtc)
564 get_default_crtc(card, *current_output);
566 int def_w, def_h;
568 if (current_plane) {
569 def_w = current_plane->w;
570 def_h = current_plane->h;
571 } else {
572 def_w = current_output->mode.hdisplay;
573 def_h = current_output->mode.vdisplay;
574 }
576 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
578 if (current_plane)
579 current_plane->fbs = fbs;
580 else
581 current_output->fbs = fbs;
583 break;
584 }
585 }
586 }
588 // create default framebuffers if needed
589 for (OutputInfo& o : outputs) {
590 if (!o.crtc) {
591 get_default_crtc(card, *current_output);
592 o.user_set_crtc = true;
593 }
595 if (o.fbs.empty() && o.user_set_crtc)
596 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
598 for (PlaneInfo &p : o.planes) {
599 if (p.fbs.empty())
600 p.fbs = get_default_fb(card, p.w, p.h);
601 }
602 }
604 return outputs;
605 }
607 static std::string videomode_to_string(const Videomode& m)
608 {
609 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
610 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
612 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
613 m.name.c_str(),
614 m.clock / 1000.0,
615 h.c_str(), v.c_str(),
616 m.vrefresh, m.calculated_vrefresh(),
617 m.flags,
618 m.type);
619 }
621 static void print_outputs(const vector<OutputInfo>& outputs)
622 {
623 for (unsigned i = 0; i < outputs.size(); ++i) {
624 const OutputInfo& o = outputs[i];
626 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
627 o.connector->fullname().c_str());
628 printf(" Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
629 if (o.primary_plane)
630 printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
631 printf(": %s\n", videomode_to_string(o.mode).c_str());
632 if (!o.fbs.empty()) {
633 auto fb = o.fbs[0];
634 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
635 PixelFormatToFourCC(fb->format()).c_str());
636 }
638 for (unsigned j = 0; j < o.planes.size(); ++j) {
639 const PlaneInfo& p = o.planes[j];
640 auto fb = p.fbs[0];
641 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
642 p.x, p.y, p.w, p.h);
643 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
644 PixelFormatToFourCC(fb->format()).c_str());
645 }
646 }
647 }
649 static void draw_test_patterns(const vector<OutputInfo>& outputs)
650 {
651 for (const OutputInfo& o : outputs) {
652 for (auto fb : o.fbs)
653 draw_test_pattern(*fb);
655 for (const PlaneInfo& p : o.planes)
656 for (auto fb : p.fbs)
657 draw_test_pattern(*fb);
658 }
659 }
661 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
662 {
663 for (const OutputInfo& o : outputs) {
664 auto conn = o.connector;
665 auto crtc = o.crtc;
667 if (!o.fbs.empty()) {
668 auto fb = o.fbs[0];
669 int r = crtc->set_mode(conn, *fb, o.mode);
670 if (r)
671 printf("crtc->set_mode() failed for crtc %u: %s\n",
672 crtc->id(), strerror(-r));
673 }
675 for (const PlaneInfo& p : o.planes) {
676 auto fb = p.fbs[0];
677 int r = crtc->set_plane(p.plane, *fb,
678 p.x, p.y, p.w, p.h,
679 0, 0, fb->width(), fb->height());
680 if (r)
681 printf("crtc->set_plane() failed for plane %u: %s\n",
682 p.plane->id(), strerror(-r));
683 }
684 }
685 }
687 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
688 {
689 // Keep blobs here so that we keep ref to them until we have committed the req
690 vector<unique_ptr<Blob>> blobs;
692 AtomicReq req(card);
694 for (const OutputInfo& o : outputs) {
695 auto conn = o.connector;
696 auto crtc = o.crtc;
698 blobs.emplace_back(o.mode.to_blob(card));
699 Blob* mode_blob = blobs.back().get();
701 req.add(conn, {
702 { "CRTC_ID", crtc->id() },
703 });
705 req.add(crtc, {
706 { "ACTIVE", 1 },
707 { "MODE_ID", mode_blob->id() },
708 });
710 if (!o.fbs.empty()) {
711 auto fb = o.fbs[0];
713 req.add(o.primary_plane, {
714 { "FB_ID", fb->id() },
715 { "CRTC_ID", crtc->id() },
716 { "SRC_X", 0 << 16 },
717 { "SRC_Y", 0 << 16 },
718 { "SRC_W", fb->width() << 16 },
719 { "SRC_H", fb->height() << 16 },
720 { "CRTC_X", 0 },
721 { "CRTC_Y", 0 },
722 { "CRTC_W", fb->width() },
723 { "CRTC_H", fb->height() },
724 });
725 }
727 for (const PlaneInfo& p : o.planes) {
728 auto fb = p.fbs[0];
730 req.add(p.plane, {
731 { "FB_ID", fb->id() },
732 { "CRTC_ID", crtc->id() },
733 { "SRC_X", 0 << 16 },
734 { "SRC_Y", 0 << 16 },
735 { "SRC_W", fb->width() << 16 },
736 { "SRC_H", fb->height() << 16 },
737 { "CRTC_X", p.x },
738 { "CRTC_Y", p.y },
739 { "CRTC_W", p.w },
740 { "CRTC_H", p.h },
741 });
742 }
743 }
745 int r;
747 r = req.test(true);
748 if (r)
749 EXIT("Atomic test failed: %d\n", r);
751 r = req.commit_sync(true);
752 if (r)
753 EXIT("Atomic commit failed: %d\n", r);
754 }
756 class FlipState : private PageFlipHandlerBase
757 {
758 public:
759 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
760 : m_card(card), m_name(name), m_outputs(outputs)
761 {
762 }
764 void start_flipping()
765 {
766 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
767 m_slowest_frame = std::chrono::duration<float>::min();
768 m_frame_num = 0;
769 queue_next();
770 }
772 private:
773 void handle_page_flip(uint32_t frame, double time)
774 {
775 m_frame_num++;
777 auto now = std::chrono::steady_clock::now();
779 std::chrono::duration<float> diff = now - m_prev_frame;
780 if (diff > m_slowest_frame)
781 m_slowest_frame = diff;
783 if (m_frame_num % 100 == 0) {
784 std::chrono::duration<float> fsec = now - m_prev_print;
785 printf("Connector %s: fps %f, slowest %.2f ms\n",
786 m_name.c_str(),
787 100.0 / fsec.count(),
788 m_slowest_frame.count() * 1000);
789 m_prev_print = now;
790 m_slowest_frame = std::chrono::duration<float>::min();
791 }
793 m_prev_frame = now;
795 queue_next();
796 }
798 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
799 {
800 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
801 }
803 static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
804 {
805 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
806 int new_xpos = get_bar_pos(fb, frame_num);
808 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
809 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
810 }
812 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
813 {
814 unsigned cur = frame_num % s_num_buffers;
816 if (!o.fbs.empty()) {
817 auto fb = o.fbs[cur];
819 draw_bar(fb, frame_num);
821 req.add(o.primary_plane, {
822 { "FB_ID", fb->id() },
823 });
824 }
826 for (const PlaneInfo& p : o.planes) {
827 auto fb = p.fbs[cur];
829 draw_bar(fb, frame_num);
831 req.add(p.plane, {
832 { "FB_ID", fb->id() },
833 });
834 }
835 }
837 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
838 {
839 unsigned cur = frame_num % s_num_buffers;
841 if (!o.fbs.empty()) {
842 auto fb = o.fbs[cur];
844 draw_bar(fb, frame_num);
846 int r = o.crtc->page_flip(*fb, this);
847 ASSERT(r == 0);
848 }
850 for (const PlaneInfo& p : o.planes) {
851 auto fb = p.fbs[cur];
853 draw_bar(fb, frame_num);
855 int r = o.crtc->set_plane(p.plane, *fb,
856 p.x, p.y, p.w, p.h,
857 0, 0, fb->width(), fb->height());
858 ASSERT(r == 0);
859 }
860 }
862 void queue_next()
863 {
864 if (m_card.has_atomic()) {
865 AtomicReq req(m_card);
867 for (auto o : m_outputs)
868 do_flip_output(req, m_frame_num, *o);
870 int r = req.commit(this);
871 if (r)
872 EXIT("Flip commit failed: %d\n", r);
873 } else {
874 ASSERT(m_outputs.size() == 1);
875 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
876 }
877 }
879 Card& m_card;
880 string m_name;
881 vector<const OutputInfo*> m_outputs;
882 unsigned m_frame_num;
884 chrono::steady_clock::time_point m_prev_print;
885 chrono::steady_clock::time_point m_prev_frame;
886 chrono::duration<float> m_slowest_frame;
888 static const unsigned bar_width = 20;
889 static const unsigned bar_speed = 8;
890 };
892 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
893 {
894 fd_set fds;
896 FD_ZERO(&fds);
898 int fd = card.fd();
900 vector<unique_ptr<FlipState>> flipstates;
902 if (!s_flip_sync) {
903 for (const OutputInfo& o : outputs) {
904 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
905 flipstates.push_back(move(fs));
906 }
907 } else {
908 vector<const OutputInfo*> ois;
910 string name;
911 for (const OutputInfo& o : outputs) {
912 name += to_string(o.connector->idx()) + ",";
913 ois.push_back(&o);
914 }
916 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
917 flipstates.push_back(move(fs));
918 }
920 for (unique_ptr<FlipState>& fs : flipstates)
921 fs->start_flipping();
923 while (true) {
924 int r;
926 FD_SET(0, &fds);
927 FD_SET(fd, &fds);
929 r = select(fd + 1, &fds, NULL, NULL, NULL);
930 if (r < 0) {
931 fprintf(stderr, "select() failed with %d: %m\n", errno);
932 break;
933 } else if (FD_ISSET(0, &fds)) {
934 fprintf(stderr, "Exit due to user-input\n");
935 break;
936 } else if (FD_ISSET(fd, &fds)) {
937 card.call_page_flip_handlers();
938 }
939 }
940 }
942 int main(int argc, char **argv)
943 {
944 vector<Arg> output_args = parse_cmdline(argc, argv);
946 Card card(s_device_path);
948 if (!card.has_atomic() && s_flip_sync)
949 EXIT("Synchronized flipping requires atomic modesetting");
951 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
953 if (card.has_atomic()) {
954 for (OutputInfo& o : outputs) {
955 o.primary_plane = o.crtc->get_primary_plane();
957 if (!o.fbs.empty() && !o.primary_plane)
958 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
959 }
960 }
962 if (!s_flip_mode)
963 draw_test_patterns(outputs);
965 print_outputs(outputs);
967 if (card.has_atomic())
968 set_crtcs_n_planes(card, outputs);
969 else
970 set_crtcs_n_planes_legacy(card, outputs);
972 printf("press enter to exit\n");
974 if (s_flip_mode)
975 main_flip(card, outputs);
976 else
977 getchar();
978 }