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:1920x1200@60
105 const regex mode_re("(?:(@?)(\\d+):)?" // @12:
106 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
107 "(?:@([\\d\\.]+))?"); // @60
109 smatch sm;
110 if (!regex_match(crtc_str, sm, mode_re))
111 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
113 if (sm[2].matched) {
114 bool use_id = sm[1].length() == 1;
115 unsigned num = stoul(sm[2].str());
117 if (use_id) {
118 Crtc* c = card.get_crtc(num);
119 if (!c)
120 EXIT("Bad crtc id '%u'", num);
122 output.crtc = c;
123 } else {
124 auto crtcs = card.get_crtcs();
126 if (num >= crtcs.size())
127 EXIT("Bad crtc number '%u'", num);
129 output.crtc = crtcs[num];
130 }
131 } else {
132 output.crtc = output.connector->get_current_crtc();
133 }
135 unsigned w = stoul(sm[3]);
136 unsigned h = stoul(sm[4]);
137 bool ilace = sm[5].matched ? true : false;
138 float refresh = sm[6].matched ? stof(sm[6]) : 0;
140 bool found_mode = false;
142 try {
143 output.mode = output.connector->get_mode(w, h, refresh, ilace);
144 found_mode = true;
145 } catch (exception& e) { }
147 if (!found_mode && s_use_dmt) {
148 try {
149 output.mode = find_dmt(w, h, refresh, ilace);
150 found_mode = true;
151 printf("Found mode from DMT\n");
152 } catch (exception& e) { }
153 }
155 if (!found_mode && s_use_cea) {
156 try {
157 output.mode = find_cea(w, h, refresh, ilace);
158 found_mode = true;
159 printf("Found mode from CEA\n");
160 } catch (exception& e) { }
161 }
163 if (!found_mode)
164 throw invalid_argument("Mode not found");
165 }
167 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
168 {
169 // 3:400,400-400x400
170 const regex plane_re("(?:(@?)(\\d+):)?" // 3:
171 "(?:(\\d+),(\\d+)-)?" // 400,400-
172 "(\\d+)x(\\d+)"); // 400x400
174 smatch sm;
175 if (!regex_match(plane_str, sm, plane_re))
176 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
178 if (sm[2].matched) {
179 bool use_id = sm[1].length() == 1;
180 unsigned num = stoul(sm[2].str());
182 if (use_id) {
183 Plane* p = card.get_plane(num);
184 if (!p)
185 EXIT("Bad plane id '%u'", num);
187 pinfo.plane = p;
188 } else {
189 auto planes = card.get_planes();
191 if (num >= planes.size())
192 EXIT("Bad plane number '%u'", num);
194 pinfo.plane = planes[num];
195 }
196 } else {
197 for (Plane* p : output.crtc->get_possible_planes()) {
198 if (s_used_planes.find(p) != s_used_planes.end())
199 continue;
201 if (p->plane_type() != PlaneType::Overlay)
202 continue;
204 pinfo.plane = p;
205 }
207 if (!pinfo.plane)
208 EXIT("Failed to find available plane");
209 }
211 s_used_planes.insert(pinfo.plane);
213 pinfo.w = stoul(sm[5]);
214 pinfo.h = stoul(sm[6]);
216 if (sm[3].matched)
217 pinfo.x = stoul(sm[3]);
218 else
219 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
221 if (sm[4].matched)
222 pinfo.y = stoul(sm[4]);
223 else
224 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
225 }
227 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
228 {
229 vector<DumbFramebuffer*> v;
231 for (unsigned i = 0; i < s_num_buffers; ++i)
232 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
234 return v;
235 }
237 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
238 {
239 unsigned w = def_w;
240 unsigned h = def_h;
241 PixelFormat format = PixelFormat::XRGB8888;
243 if (!fb_str.empty()) {
244 // XXX the regexp is not quite correct
245 // 400x400-NV12
246 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400
247 "(?:-)?" // -
248 "(\\w\\w\\w\\w)?"); // NV12
250 smatch sm;
251 if (!regex_match(fb_str, sm, fb_re))
252 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
254 if (sm[1].matched)
255 w = stoul(sm[1]);
256 if (sm[2].matched)
257 h = stoul(sm[2]);
258 if (sm[3].matched)
259 format = FourCCToPixelFormat(sm[3]);
260 }
262 vector<DumbFramebuffer*> v;
264 for (unsigned i = 0; i < s_num_buffers; ++i)
265 v.push_back(new DumbFramebuffer(card, w, h, format));
267 return v;
268 }
270 static const char* usage_str =
271 "Usage: kmstest [OPTION]...\n\n"
272 "Show a test pattern on a display or plane\n\n"
273 "Options:\n"
274 " --device=DEVICE DEVICE is the path to DRM card to open\n"
275 " -c, --connector=CONN CONN is <connector>\n"
276 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
277 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
278 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
279 " --dmt Search for the given mode from DMT tables\n"
280 " --cea Search for the given mode from CEA tables\n"
281 " --flip Do page flipping for each output\n"
282 " --sync Synchronize page flipping\n"
283 "\n"
284 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
285 "<connector> can also be given by name.\n"
286 "\n"
287 "Options can be given multiple times to set up multiple displays or planes.\n"
288 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
289 "an earlier option.\n"
290 "If you omit parameters, kmstest tries to guess what you mean\n"
291 "\n"
292 "Examples:\n"
293 "\n"
294 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
295 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
296 "XR24 framebuffer on first connected connector in the default mode:\n"
297 " kmstest -f XR24\n\n"
298 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
299 " kmstest -p 400x400 -f XR24\n\n"
300 "Test pattern on the second connector with default mode:\n"
301 " kmstest -c 1\n"
302 ;
304 static void usage()
305 {
306 puts(usage_str);
307 }
309 enum class ObjectType
310 {
311 Connector,
312 Crtc,
313 Plane,
314 Framebuffer,
315 };
317 struct Arg
318 {
319 ObjectType type;
320 string arg;
321 };
323 static string s_device_path = "/dev/dri/card0";
325 static vector<Arg> parse_cmdline(int argc, char **argv)
326 {
327 vector<Arg> args;
329 OptionSet optionset = {
330 Option("|device=",
331 [&](string s)
332 {
333 s_device_path = s;
334 }),
335 Option("c|connector=",
336 [&](string s)
337 {
338 args.push_back(Arg { ObjectType::Connector, s });
339 }),
340 Option("r|crtc=", [&](string s)
341 {
342 args.push_back(Arg { ObjectType::Crtc, s });
343 }),
344 Option("p|plane=", [&](string s)
345 {
346 args.push_back(Arg { ObjectType::Plane, s });
347 }),
348 Option("f|fb=", [&](string s)
349 {
350 args.push_back(Arg { ObjectType::Framebuffer, s });
351 }),
352 Option("|dmt", []()
353 {
354 s_use_dmt = true;
355 }),
356 Option("|cea", []()
357 {
358 s_use_cea = true;
359 }),
360 Option("|flip", []()
361 {
362 s_flip_mode = true;
363 s_num_buffers = 2;
364 }),
365 Option("|sync", []()
366 {
367 s_flip_sync = true;
368 }),
369 Option("h|help", [&]()
370 {
371 usage();
372 exit(-1);
373 }),
374 };
376 optionset.parse(argc, argv);
378 if (optionset.params().size() > 0) {
379 usage();
380 exit(-1);
381 }
383 return args;
384 }
386 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
387 {
388 vector<OutputInfo> outputs;
390 if (output_args.size() == 0) {
391 // no output args, show a pattern on all screens
392 for (auto& pipe : card.get_connected_pipelines()) {
393 OutputInfo output = { };
394 output.connector = pipe.connector;
395 output.crtc = pipe.crtc;
396 output.mode = output.connector->get_default_mode();
398 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
400 outputs.push_back(output);
401 }
403 return outputs;
404 }
406 OutputInfo* current_output = 0;
407 PlaneInfo* current_plane = 0;
409 for (auto& arg : output_args) {
410 switch (arg.type) {
411 case ObjectType::Connector:
412 {
413 outputs.push_back(OutputInfo { });
414 current_output = &outputs.back();
416 parse_connector(card, arg.arg, *current_output);
417 current_plane = 0;
419 break;
420 }
422 case ObjectType::Crtc:
423 {
424 if (!current_output) {
425 outputs.push_back(OutputInfo { });
426 current_output = &outputs.back();
427 }
429 if (!current_output->connector)
430 get_default_connector(card, *current_output);
432 parse_crtc(card, arg.arg, *current_output);
434 current_output->user_set_crtc = true;
436 current_plane = 0;
438 break;
439 }
441 case ObjectType::Plane:
442 {
443 if (!current_output) {
444 outputs.push_back(OutputInfo { });
445 current_output = &outputs.back();
446 }
448 if (!current_output->connector)
449 get_default_connector(card, *current_output);
451 if (!current_output->crtc)
452 get_default_crtc(card, *current_output);
454 current_output->planes.push_back(PlaneInfo { });
455 current_plane = ¤t_output->planes.back();
457 parse_plane(card, arg.arg, *current_output, *current_plane);
459 break;
460 }
462 case ObjectType::Framebuffer:
463 {
464 if (!current_output) {
465 outputs.push_back(OutputInfo { });
466 current_output = &outputs.back();
467 }
469 if (!current_output->connector)
470 get_default_connector(card, *current_output);
472 if (!current_output->crtc)
473 get_default_crtc(card, *current_output);
475 int def_w, def_h;
477 if (current_plane) {
478 def_w = current_plane->w;
479 def_h = current_plane->h;
480 } else {
481 def_w = current_output->mode.hdisplay;
482 def_h = current_output->mode.vdisplay;
483 }
485 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
487 if (current_plane)
488 current_plane->fbs = fbs;
489 else
490 current_output->fbs = fbs;
492 break;
493 }
494 }
495 }
497 // create default framebuffers if needed
498 for (OutputInfo& o : outputs) {
499 if (!o.crtc) {
500 get_default_crtc(card, *current_output);
501 o.user_set_crtc = true;
502 }
504 if (o.fbs.empty() && o.user_set_crtc)
505 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
507 for (PlaneInfo &p : o.planes) {
508 if (p.fbs.empty())
509 p.fbs = get_default_fb(card, p.w, p.h);
510 }
511 }
513 return outputs;
514 }
516 static std::string videomode_to_string(const Videomode& m)
517 {
518 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
519 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
521 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
522 m.name.c_str(),
523 m.clock / 1000.0,
524 h.c_str(), v.c_str(),
525 m.vrefresh, m.calculated_vrefresh(),
526 m.flags,
527 m.type);
528 }
530 static void print_outputs(const vector<OutputInfo>& outputs)
531 {
532 for (unsigned i = 0; i < outputs.size(); ++i) {
533 const OutputInfo& o = outputs[i];
535 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
536 o.connector->fullname().c_str());
537 printf(" Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
538 if (o.primary_plane)
539 printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
540 printf(": %s\n", videomode_to_string(o.mode).c_str());
541 if (!o.fbs.empty()) {
542 auto fb = o.fbs[0];
543 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
544 PixelFormatToFourCC(fb->format()).c_str());
545 }
547 for (unsigned j = 0; j < o.planes.size(); ++j) {
548 const PlaneInfo& p = o.planes[j];
549 auto fb = p.fbs[0];
550 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
551 p.x, p.y, p.w, p.h);
552 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
553 PixelFormatToFourCC(fb->format()).c_str());
554 }
555 }
556 }
558 static void draw_test_patterns(const vector<OutputInfo>& outputs)
559 {
560 for (const OutputInfo& o : outputs) {
561 for (auto fb : o.fbs)
562 draw_test_pattern(*fb);
564 for (const PlaneInfo& p : o.planes)
565 for (auto fb : p.fbs)
566 draw_test_pattern(*fb);
567 }
568 }
570 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
571 {
572 for (const OutputInfo& o : outputs) {
573 auto conn = o.connector;
574 auto crtc = o.crtc;
576 if (!o.fbs.empty()) {
577 auto fb = o.fbs[0];
578 int r = crtc->set_mode(conn, *fb, o.mode);
579 if (r)
580 printf("crtc->set_mode() failed for crtc %u: %s\n",
581 crtc->id(), strerror(-r));
582 }
584 for (const PlaneInfo& p : o.planes) {
585 auto fb = p.fbs[0];
586 int r = crtc->set_plane(p.plane, *fb,
587 p.x, p.y, p.w, p.h,
588 0, 0, fb->width(), fb->height());
589 if (r)
590 printf("crtc->set_plane() failed for plane %u: %s\n",
591 p.plane->id(), strerror(-r));
592 }
593 }
594 }
596 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
597 {
598 // Keep blobs here so that we keep ref to them until we have committed the req
599 vector<unique_ptr<Blob>> blobs;
601 AtomicReq req(card);
603 for (const OutputInfo& o : outputs) {
604 auto conn = o.connector;
605 auto crtc = o.crtc;
607 if (!o.fbs.empty()) {
608 auto fb = o.fbs[0];
610 blobs.emplace_back(o.mode.to_blob(card));
611 Blob* mode_blob = blobs.back().get();
613 req.add(conn, {
614 { "CRTC_ID", crtc->id() },
615 });
617 req.add(crtc, {
618 { "ACTIVE", 1 },
619 { "MODE_ID", mode_blob->id() },
620 });
622 req.add(o.primary_plane, {
623 { "FB_ID", fb->id() },
624 { "CRTC_ID", crtc->id() },
625 { "SRC_X", 0 << 16 },
626 { "SRC_Y", 0 << 16 },
627 { "SRC_W", fb->width() << 16 },
628 { "SRC_H", fb->height() << 16 },
629 { "CRTC_X", 0 },
630 { "CRTC_Y", 0 },
631 { "CRTC_W", fb->width() },
632 { "CRTC_H", fb->height() },
633 });
634 }
636 for (const PlaneInfo& p : o.planes) {
637 auto fb = p.fbs[0];
639 req.add(p.plane, {
640 { "FB_ID", fb->id() },
641 { "CRTC_ID", crtc->id() },
642 { "SRC_X", 0 << 16 },
643 { "SRC_Y", 0 << 16 },
644 { "SRC_W", fb->width() << 16 },
645 { "SRC_H", fb->height() << 16 },
646 { "CRTC_X", p.x },
647 { "CRTC_Y", p.y },
648 { "CRTC_W", p.w },
649 { "CRTC_H", p.h },
650 });
651 }
652 }
654 int r;
656 r = req.test(true);
657 if (r)
658 EXIT("Atomic test failed: %d\n", r);
660 r = req.commit_sync(true);
661 if (r)
662 EXIT("Atomic commit failed: %d\n", r);
663 }
665 class FlipState : private PageFlipHandlerBase
666 {
667 public:
668 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
669 : m_card(card), m_name(name), m_outputs(outputs)
670 {
671 }
673 void start_flipping()
674 {
675 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
676 m_slowest_frame = std::chrono::duration<float>::min();
677 m_frame_num = 0;
678 queue_next();
679 }
681 private:
682 void handle_page_flip(uint32_t frame, double time)
683 {
684 m_frame_num++;
686 auto now = std::chrono::steady_clock::now();
688 std::chrono::duration<float> diff = now - m_prev_frame;
689 if (diff > m_slowest_frame)
690 m_slowest_frame = diff;
692 if (m_frame_num % 100 == 0) {
693 std::chrono::duration<float> fsec = now - m_prev_print;
694 printf("Connector %s: fps %f, slowest %.2f ms\n",
695 m_name.c_str(),
696 100.0 / fsec.count(),
697 m_slowest_frame.count() * 1000);
698 m_prev_print = now;
699 m_slowest_frame = std::chrono::duration<float>::min();
700 }
702 m_prev_frame = now;
704 queue_next();
705 }
707 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
708 {
709 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
710 }
712 static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
713 {
714 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
715 int new_xpos = get_bar_pos(fb, frame_num);
717 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
718 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
719 }
721 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
722 {
723 unsigned cur = frame_num % s_num_buffers;
725 if (!o.fbs.empty()) {
726 auto fb = o.fbs[cur];
728 draw_bar(fb, frame_num);
730 req.add(o.primary_plane, {
731 { "FB_ID", fb->id() },
732 });
733 }
735 for (const PlaneInfo& p : o.planes) {
736 auto fb = p.fbs[cur];
738 draw_bar(fb, frame_num);
740 req.add(p.plane, {
741 { "FB_ID", fb->id() },
742 });
743 }
744 }
746 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
747 {
748 unsigned cur = frame_num % s_num_buffers;
750 if (!o.fbs.empty()) {
751 auto fb = o.fbs[cur];
753 draw_bar(fb, frame_num);
755 int r = o.crtc->page_flip(*fb, this);
756 ASSERT(r == 0);
757 }
759 for (const PlaneInfo& p : o.planes) {
760 auto fb = p.fbs[cur];
762 draw_bar(fb, frame_num);
764 int r = o.crtc->set_plane(p.plane, *fb,
765 p.x, p.y, p.w, p.h,
766 0, 0, fb->width(), fb->height());
767 ASSERT(r == 0);
768 }
769 }
771 void queue_next()
772 {
773 if (m_card.has_atomic()) {
774 AtomicReq req(m_card);
776 for (auto o : m_outputs)
777 do_flip_output(req, m_frame_num, *o);
779 int r = req.commit(this);
780 if (r)
781 EXIT("Flip commit failed: %d\n", r);
782 } else {
783 ASSERT(m_outputs.size() == 1);
784 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
785 }
786 }
788 Card& m_card;
789 string m_name;
790 vector<const OutputInfo*> m_outputs;
791 unsigned m_frame_num;
793 chrono::steady_clock::time_point m_prev_print;
794 chrono::steady_clock::time_point m_prev_frame;
795 chrono::duration<float> m_slowest_frame;
797 static const unsigned bar_width = 20;
798 static const unsigned bar_speed = 8;
799 };
801 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
802 {
803 fd_set fds;
805 FD_ZERO(&fds);
807 int fd = card.fd();
809 vector<unique_ptr<FlipState>> flipstates;
811 if (!s_flip_sync) {
812 for (const OutputInfo& o : outputs) {
813 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
814 flipstates.push_back(move(fs));
815 }
816 } else {
817 vector<const OutputInfo*> ois;
819 string name;
820 for (const OutputInfo& o : outputs) {
821 name += to_string(o.connector->idx()) + ",";
822 ois.push_back(&o);
823 }
825 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
826 flipstates.push_back(move(fs));
827 }
829 for (unique_ptr<FlipState>& fs : flipstates)
830 fs->start_flipping();
832 while (true) {
833 int r;
835 FD_SET(0, &fds);
836 FD_SET(fd, &fds);
838 r = select(fd + 1, &fds, NULL, NULL, NULL);
839 if (r < 0) {
840 fprintf(stderr, "select() failed with %d: %m\n", errno);
841 break;
842 } else if (FD_ISSET(0, &fds)) {
843 fprintf(stderr, "Exit due to user-input\n");
844 break;
845 } else if (FD_ISSET(fd, &fds)) {
846 card.call_page_flip_handlers();
847 }
848 }
849 }
851 int main(int argc, char **argv)
852 {
853 vector<Arg> output_args = parse_cmdline(argc, argv);
855 Card card(s_device_path);
857 if (!card.has_atomic() && s_flip_sync)
858 EXIT("Synchronized flipping requires atomic modesetting");
860 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
862 if (card.has_atomic()) {
863 for (OutputInfo& o : outputs) {
864 o.primary_plane = o.crtc->get_primary_plane();
866 if (!o.fbs.empty() && !o.primary_plane)
867 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
868 }
869 }
871 if (!s_flip_mode)
872 draw_test_patterns(outputs);
874 print_outputs(outputs);
876 if (card.has_atomic())
877 set_crtcs_n_planes(card, outputs);
878 else
879 set_crtcs_n_planes_legacy(card, outputs);
881 printf("press enter to exit\n");
883 if (s_flip_mode)
884 main_flip(card, outputs);
885 else
886 getchar();
887 }