52e4614f9f49f8ea45b223aa520ef9de927aa269
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>
13 #include <kms++util/kms++util.h>
15 using namespace std;
16 using namespace kms;
18 struct PlaneInfo
19 {
20 Plane* plane;
22 unsigned x;
23 unsigned y;
24 unsigned w;
25 unsigned h;
27 vector<DumbFramebuffer*> fbs;
28 };
30 struct OutputInfo
31 {
32 Connector* connector;
34 Crtc* crtc;
35 Plane* primary_plane;
36 Videomode mode;
37 bool user_set_crtc;
38 vector<DumbFramebuffer*> fbs;
40 vector<PlaneInfo> planes;
41 };
43 static bool s_use_dmt;
44 static bool s_use_cea;
45 static unsigned s_num_buffers = 1;
46 static bool s_flip_mode;
47 static bool s_flip_sync;
49 static set<Crtc*> s_used_crtcs;
50 static set<Plane*> s_used_planes;
52 __attribute__ ((unused))
53 static void print_regex_match(smatch sm)
54 {
55 for (unsigned i = 0; i < sm.size(); ++i) {
56 string str = sm[i].str();
57 printf("%u: %s\n", i, str.c_str());
58 }
59 }
61 static void get_default_connector(Card& card, OutputInfo& output)
62 {
63 output.connector = card.get_first_connected_connector();
64 output.mode = output.connector->get_default_mode();
65 }
67 static void parse_connector(Card& card, const string& str, OutputInfo& output)
68 {
69 Connector* conn = resolve_connector(card, str);
71 if (!conn)
72 EXIT("No connector '%s'", str.c_str());
74 if (!conn->connected())
75 EXIT("Connector '%s' not connected", conn->fullname().c_str());
77 output.connector = conn;
78 output.mode = output.connector->get_default_mode();
79 }
81 static void get_default_crtc(Card& card, OutputInfo& output)
82 {
83 Crtc* crtc = output.connector->get_current_crtc();
85 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
86 s_used_crtcs.insert(crtc);
87 output.crtc = crtc;
88 return;
89 }
91 for (const auto& possible : output.connector->get_possible_crtcs()) {
92 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
93 s_used_crtcs.insert(possible);
94 output.crtc = possible;
95 return;
96 }
97 }
99 EXIT("Could not find available crtc");
100 }
102 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
103 {
104 // @12:1920x1200i@60
105 // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
107 const regex modename_re("(?:(@?)(\\d+):)?" // @12:
108 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
109 "(?:@([\\d\\.]+))?"); // @60
111 const regex modeline_re("(?:(@?)(\\d+):)?" // @12:
112 "(\\d+)," // 33000000,
113 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])," // 800/210/30/16/-,
114 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])" // 480/22/13/10/-
115 "(?:,([i]+))?" // ,i
116 );
118 smatch sm;
119 if (regex_match(crtc_str, sm, modename_re)) {
120 if (sm[2].matched) {
121 bool use_id = sm[1].length() == 1;
122 unsigned num = stoul(sm[2].str());
124 if (use_id) {
125 Crtc* c = card.get_crtc(num);
126 if (!c)
127 EXIT("Bad crtc id '%u'", num);
129 output.crtc = c;
130 } else {
131 auto crtcs = card.get_crtcs();
133 if (num >= crtcs.size())
134 EXIT("Bad crtc number '%u'", num);
136 output.crtc = crtcs[num];
137 }
138 } else {
139 output.crtc = output.connector->get_current_crtc();
140 }
142 unsigned w = stoul(sm[3]);
143 unsigned h = stoul(sm[4]);
144 bool ilace = sm[5].matched ? true : false;
145 float refresh = sm[6].matched ? stof(sm[6]) : 0;
147 bool found_mode = false;
149 try {
150 output.mode = output.connector->get_mode(w, h, refresh, ilace);
151 found_mode = true;
152 } catch (exception& e) { }
154 if (!found_mode && s_use_dmt) {
155 try {
156 output.mode = find_dmt(w, h, refresh, ilace);
157 found_mode = true;
158 printf("Found mode from DMT\n");
159 } catch (exception& e) { }
160 }
162 if (!found_mode && s_use_cea) {
163 try {
164 output.mode = find_cea(w, h, refresh, ilace);
165 found_mode = true;
166 printf("Found mode from CEA\n");
167 } catch (exception& e) { }
168 }
170 if (!found_mode)
171 throw invalid_argument("Mode not found");
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<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
292 {
293 vector<DumbFramebuffer*> 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<DumbFramebuffer*> 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<DumbFramebuffer*> 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 const char* usage_str =
335 "Usage: kmstest [OPTION]...\n\n"
336 "Show a test pattern on a display or plane\n\n"
337 "Options:\n"
338 " --device=DEVICE DEVICE is the path to DRM card to open\n"
339 " -c, --connector=CONN CONN is <connector>\n"
340 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
341 " or\n"
342 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
343 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
344 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
345 " --dmt Search for the given mode from DMT tables\n"
346 " --cea Search for the given mode from CEA tables\n"
347 " --flip Do page flipping for each output\n"
348 " --sync Synchronize page flipping\n"
349 "\n"
350 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
351 "<connector> can also be given by name.\n"
352 "\n"
353 "Options can be given multiple times to set up multiple displays or planes.\n"
354 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
355 "an earlier option.\n"
356 "If you omit parameters, kmstest tries to guess what you mean\n"
357 "\n"
358 "Examples:\n"
359 "\n"
360 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
361 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
362 "XR24 framebuffer on first connected connector in the default mode:\n"
363 " kmstest -f XR24\n\n"
364 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
365 " kmstest -p 400x400 -f XR24\n\n"
366 "Test pattern on the second connector with default mode:\n"
367 " kmstest -c 1\n"
368 ;
370 static void usage()
371 {
372 puts(usage_str);
373 }
375 enum class ObjectType
376 {
377 Connector,
378 Crtc,
379 Plane,
380 Framebuffer,
381 };
383 struct Arg
384 {
385 ObjectType type;
386 string arg;
387 };
389 static string s_device_path = "/dev/dri/card0";
391 static vector<Arg> parse_cmdline(int argc, char **argv)
392 {
393 vector<Arg> args;
395 OptionSet optionset = {
396 Option("|device=",
397 [&](string s)
398 {
399 s_device_path = s;
400 }),
401 Option("c|connector=",
402 [&](string s)
403 {
404 args.push_back(Arg { ObjectType::Connector, s });
405 }),
406 Option("r|crtc=", [&](string s)
407 {
408 args.push_back(Arg { ObjectType::Crtc, s });
409 }),
410 Option("p|plane=", [&](string s)
411 {
412 args.push_back(Arg { ObjectType::Plane, s });
413 }),
414 Option("f|fb=", [&](string s)
415 {
416 args.push_back(Arg { ObjectType::Framebuffer, s });
417 }),
418 Option("|dmt", []()
419 {
420 s_use_dmt = true;
421 }),
422 Option("|cea", []()
423 {
424 s_use_cea = true;
425 }),
426 Option("|flip", []()
427 {
428 s_flip_mode = true;
429 s_num_buffers = 2;
430 }),
431 Option("|sync", []()
432 {
433 s_flip_sync = true;
434 }),
435 Option("h|help", [&]()
436 {
437 usage();
438 exit(-1);
439 }),
440 };
442 optionset.parse(argc, argv);
444 if (optionset.params().size() > 0) {
445 usage();
446 exit(-1);
447 }
449 return args;
450 }
452 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
453 {
454 vector<OutputInfo> outputs;
456 if (output_args.size() == 0) {
457 // no output args, show a pattern on all screens
458 for (auto& pipe : card.get_connected_pipelines()) {
459 OutputInfo output = { };
460 output.connector = pipe.connector;
461 output.crtc = pipe.crtc;
462 output.mode = output.connector->get_default_mode();
464 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
466 outputs.push_back(output);
467 }
469 return outputs;
470 }
472 OutputInfo* current_output = 0;
473 PlaneInfo* current_plane = 0;
475 for (auto& arg : output_args) {
476 switch (arg.type) {
477 case ObjectType::Connector:
478 {
479 outputs.push_back(OutputInfo { });
480 current_output = &outputs.back();
482 parse_connector(card, arg.arg, *current_output);
483 current_plane = 0;
485 break;
486 }
488 case ObjectType::Crtc:
489 {
490 if (!current_output) {
491 outputs.push_back(OutputInfo { });
492 current_output = &outputs.back();
493 }
495 if (!current_output->connector)
496 get_default_connector(card, *current_output);
498 parse_crtc(card, arg.arg, *current_output);
500 current_output->user_set_crtc = true;
502 current_plane = 0;
504 break;
505 }
507 case ObjectType::Plane:
508 {
509 if (!current_output) {
510 outputs.push_back(OutputInfo { });
511 current_output = &outputs.back();
512 }
514 if (!current_output->connector)
515 get_default_connector(card, *current_output);
517 if (!current_output->crtc)
518 get_default_crtc(card, *current_output);
520 current_output->planes.push_back(PlaneInfo { });
521 current_plane = ¤t_output->planes.back();
523 parse_plane(card, arg.arg, *current_output, *current_plane);
525 break;
526 }
528 case ObjectType::Framebuffer:
529 {
530 if (!current_output) {
531 outputs.push_back(OutputInfo { });
532 current_output = &outputs.back();
533 }
535 if (!current_output->connector)
536 get_default_connector(card, *current_output);
538 if (!current_output->crtc)
539 get_default_crtc(card, *current_output);
541 int def_w, def_h;
543 if (current_plane) {
544 def_w = current_plane->w;
545 def_h = current_plane->h;
546 } else {
547 def_w = current_output->mode.hdisplay;
548 def_h = current_output->mode.vdisplay;
549 }
551 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
553 if (current_plane)
554 current_plane->fbs = fbs;
555 else
556 current_output->fbs = fbs;
558 break;
559 }
560 }
561 }
563 // create default framebuffers if needed
564 for (OutputInfo& o : outputs) {
565 if (!o.crtc) {
566 get_default_crtc(card, *current_output);
567 o.user_set_crtc = true;
568 }
570 if (o.fbs.empty() && o.user_set_crtc)
571 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
573 for (PlaneInfo &p : o.planes) {
574 if (p.fbs.empty())
575 p.fbs = get_default_fb(card, p.w, p.h);
576 }
577 }
579 return outputs;
580 }
582 static std::string videomode_to_string(const Videomode& m)
583 {
584 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
585 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
587 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
588 m.name.c_str(),
589 m.clock / 1000.0,
590 h.c_str(), v.c_str(),
591 m.vrefresh, m.calculated_vrefresh(),
592 m.flags,
593 m.type);
594 }
596 static void print_outputs(const vector<OutputInfo>& outputs)
597 {
598 for (unsigned i = 0; i < outputs.size(); ++i) {
599 const OutputInfo& o = outputs[i];
601 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
602 o.connector->fullname().c_str());
603 printf(" Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
604 if (o.primary_plane)
605 printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
606 printf(": %s\n", videomode_to_string(o.mode).c_str());
607 if (!o.fbs.empty()) {
608 auto fb = o.fbs[0];
609 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
610 PixelFormatToFourCC(fb->format()).c_str());
611 }
613 for (unsigned j = 0; j < o.planes.size(); ++j) {
614 const PlaneInfo& p = o.planes[j];
615 auto fb = p.fbs[0];
616 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
617 p.x, p.y, p.w, p.h);
618 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
619 PixelFormatToFourCC(fb->format()).c_str());
620 }
621 }
622 }
624 static void draw_test_patterns(const vector<OutputInfo>& outputs)
625 {
626 for (const OutputInfo& o : outputs) {
627 for (auto fb : o.fbs)
628 draw_test_pattern(*fb);
630 for (const PlaneInfo& p : o.planes)
631 for (auto fb : p.fbs)
632 draw_test_pattern(*fb);
633 }
634 }
636 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
637 {
638 for (const OutputInfo& o : outputs) {
639 auto conn = o.connector;
640 auto crtc = o.crtc;
642 if (!o.fbs.empty()) {
643 auto fb = o.fbs[0];
644 int r = crtc->set_mode(conn, *fb, o.mode);
645 if (r)
646 printf("crtc->set_mode() failed for crtc %u: %s\n",
647 crtc->id(), strerror(-r));
648 }
650 for (const PlaneInfo& p : o.planes) {
651 auto fb = p.fbs[0];
652 int r = crtc->set_plane(p.plane, *fb,
653 p.x, p.y, p.w, p.h,
654 0, 0, fb->width(), fb->height());
655 if (r)
656 printf("crtc->set_plane() failed for plane %u: %s\n",
657 p.plane->id(), strerror(-r));
658 }
659 }
660 }
662 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
663 {
664 // Keep blobs here so that we keep ref to them until we have committed the req
665 vector<unique_ptr<Blob>> blobs;
667 AtomicReq req(card);
669 for (const OutputInfo& o : outputs) {
670 auto conn = o.connector;
671 auto crtc = o.crtc;
673 blobs.emplace_back(o.mode.to_blob(card));
674 Blob* mode_blob = blobs.back().get();
676 req.add(conn, {
677 { "CRTC_ID", crtc->id() },
678 });
680 req.add(crtc, {
681 { "ACTIVE", 1 },
682 { "MODE_ID", mode_blob->id() },
683 });
685 if (!o.fbs.empty()) {
686 auto fb = o.fbs[0];
688 req.add(o.primary_plane, {
689 { "FB_ID", fb->id() },
690 { "CRTC_ID", crtc->id() },
691 { "SRC_X", 0 << 16 },
692 { "SRC_Y", 0 << 16 },
693 { "SRC_W", fb->width() << 16 },
694 { "SRC_H", fb->height() << 16 },
695 { "CRTC_X", 0 },
696 { "CRTC_Y", 0 },
697 { "CRTC_W", fb->width() },
698 { "CRTC_H", fb->height() },
699 });
700 }
702 for (const PlaneInfo& p : o.planes) {
703 auto fb = p.fbs[0];
705 req.add(p.plane, {
706 { "FB_ID", fb->id() },
707 { "CRTC_ID", crtc->id() },
708 { "SRC_X", 0 << 16 },
709 { "SRC_Y", 0 << 16 },
710 { "SRC_W", fb->width() << 16 },
711 { "SRC_H", fb->height() << 16 },
712 { "CRTC_X", p.x },
713 { "CRTC_Y", p.y },
714 { "CRTC_W", p.w },
715 { "CRTC_H", p.h },
716 });
717 }
718 }
720 int r;
722 r = req.test(true);
723 if (r)
724 EXIT("Atomic test failed: %d\n", r);
726 r = req.commit_sync(true);
727 if (r)
728 EXIT("Atomic commit failed: %d\n", r);
729 }
731 class FlipState : private PageFlipHandlerBase
732 {
733 public:
734 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
735 : m_card(card), m_name(name), m_outputs(outputs)
736 {
737 }
739 void start_flipping()
740 {
741 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
742 m_slowest_frame = std::chrono::duration<float>::min();
743 m_frame_num = 0;
744 queue_next();
745 }
747 private:
748 void handle_page_flip(uint32_t frame, double time)
749 {
750 m_frame_num++;
752 auto now = std::chrono::steady_clock::now();
754 std::chrono::duration<float> diff = now - m_prev_frame;
755 if (diff > m_slowest_frame)
756 m_slowest_frame = diff;
758 if (m_frame_num % 100 == 0) {
759 std::chrono::duration<float> fsec = now - m_prev_print;
760 printf("Connector %s: fps %f, slowest %.2f ms\n",
761 m_name.c_str(),
762 100.0 / fsec.count(),
763 m_slowest_frame.count() * 1000);
764 m_prev_print = now;
765 m_slowest_frame = std::chrono::duration<float>::min();
766 }
768 m_prev_frame = now;
770 queue_next();
771 }
773 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
774 {
775 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
776 }
778 static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
779 {
780 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
781 int new_xpos = get_bar_pos(fb, frame_num);
783 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
784 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
785 }
787 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
788 {
789 unsigned cur = frame_num % s_num_buffers;
791 if (!o.fbs.empty()) {
792 auto fb = o.fbs[cur];
794 draw_bar(fb, frame_num);
796 req.add(o.primary_plane, {
797 { "FB_ID", fb->id() },
798 });
799 }
801 for (const PlaneInfo& p : o.planes) {
802 auto fb = p.fbs[cur];
804 draw_bar(fb, frame_num);
806 req.add(p.plane, {
807 { "FB_ID", fb->id() },
808 });
809 }
810 }
812 void do_flip_output_legacy(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 int r = o.crtc->page_flip(*fb, this);
822 ASSERT(r == 0);
823 }
825 for (const PlaneInfo& p : o.planes) {
826 auto fb = p.fbs[cur];
828 draw_bar(fb, frame_num);
830 int r = o.crtc->set_plane(p.plane, *fb,
831 p.x, p.y, p.w, p.h,
832 0, 0, fb->width(), fb->height());
833 ASSERT(r == 0);
834 }
835 }
837 void queue_next()
838 {
839 if (m_card.has_atomic()) {
840 AtomicReq req(m_card);
842 for (auto o : m_outputs)
843 do_flip_output(req, m_frame_num, *o);
845 int r = req.commit(this);
846 if (r)
847 EXIT("Flip commit failed: %d\n", r);
848 } else {
849 ASSERT(m_outputs.size() == 1);
850 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
851 }
852 }
854 Card& m_card;
855 string m_name;
856 vector<const OutputInfo*> m_outputs;
857 unsigned m_frame_num;
859 chrono::steady_clock::time_point m_prev_print;
860 chrono::steady_clock::time_point m_prev_frame;
861 chrono::duration<float> m_slowest_frame;
863 static const unsigned bar_width = 20;
864 static const unsigned bar_speed = 8;
865 };
867 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
868 {
869 fd_set fds;
871 FD_ZERO(&fds);
873 int fd = card.fd();
875 vector<unique_ptr<FlipState>> flipstates;
877 if (!s_flip_sync) {
878 for (const OutputInfo& o : outputs) {
879 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
880 flipstates.push_back(move(fs));
881 }
882 } else {
883 vector<const OutputInfo*> ois;
885 string name;
886 for (const OutputInfo& o : outputs) {
887 name += to_string(o.connector->idx()) + ",";
888 ois.push_back(&o);
889 }
891 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
892 flipstates.push_back(move(fs));
893 }
895 for (unique_ptr<FlipState>& fs : flipstates)
896 fs->start_flipping();
898 while (true) {
899 int r;
901 FD_SET(0, &fds);
902 FD_SET(fd, &fds);
904 r = select(fd + 1, &fds, NULL, NULL, NULL);
905 if (r < 0) {
906 fprintf(stderr, "select() failed with %d: %m\n", errno);
907 break;
908 } else if (FD_ISSET(0, &fds)) {
909 fprintf(stderr, "Exit due to user-input\n");
910 break;
911 } else if (FD_ISSET(fd, &fds)) {
912 card.call_page_flip_handlers();
913 }
914 }
915 }
917 int main(int argc, char **argv)
918 {
919 vector<Arg> output_args = parse_cmdline(argc, argv);
921 Card card(s_device_path);
923 if (!card.has_atomic() && s_flip_sync)
924 EXIT("Synchronized flipping requires atomic modesetting");
926 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
928 if (card.has_atomic()) {
929 for (OutputInfo& o : outputs) {
930 o.primary_plane = o.crtc->get_primary_plane();
932 if (!o.fbs.empty() && !o.primary_plane)
933 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
934 }
935 }
937 if (!s_flip_mode)
938 draw_test_patterns(outputs);
940 print_outputs(outputs);
942 if (card.has_atomic())
943 set_crtcs_n_planes(card, outputs);
944 else
945 set_crtcs_n_planes_legacy(card, outputs);
947 printf("press enter to exit\n");
949 if (s_flip_mode)
950 main_flip(card, outputs);
951 else
952 getchar();
953 }