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+):)?(?:(\\d+)x(\\d+)(i)?)(?:@([\\d\\.]+))?");
107 smatch sm;
108 if (!regex_match(crtc_str, sm, mode_re))
109 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
111 if (sm[2].matched) {
112 bool use_id = sm[1].length() == 1;
113 unsigned num = stoul(sm[2].str());
115 if (use_id) {
116 Crtc* c = card.get_crtc(num);
117 if (!c)
118 EXIT("Bad crtc id '%u'", num);
120 output.crtc = c;
121 } else {
122 auto crtcs = card.get_crtcs();
124 if (num >= crtcs.size())
125 EXIT("Bad crtc number '%u'", num);
127 output.crtc = crtcs[num];
128 }
129 } else {
130 output.crtc = output.connector->get_current_crtc();
131 }
133 unsigned w = stoul(sm[3]);
134 unsigned h = stoul(sm[4]);
135 bool ilace = sm[5].matched ? true : false;
136 float refresh = sm[6].matched ? stof(sm[6]) : 0;
138 bool found_mode = false;
140 try {
141 output.mode = output.connector->get_mode(w, h, refresh, ilace);
142 found_mode = true;
143 } catch (exception& e) { }
145 if (!found_mode && s_use_dmt) {
146 try {
147 output.mode = find_dmt(w, h, refresh, ilace);
148 found_mode = true;
149 printf("Found mode from DMT\n");
150 } catch (exception& e) { }
151 }
153 if (!found_mode && s_use_cea) {
154 try {
155 output.mode = find_cea(w, h, refresh, ilace);
156 found_mode = true;
157 printf("Found mode from CEA\n");
158 } catch (exception& e) { }
159 }
161 if (!found_mode)
162 throw invalid_argument("Mode not found");
163 }
165 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
166 {
167 // 3:400,400-400x400
168 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
170 smatch sm;
171 if (!regex_match(plane_str, sm, plane_re))
172 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
174 if (sm[2].matched) {
175 bool use_id = sm[1].length() == 1;
176 unsigned num = stoul(sm[2].str());
178 if (use_id) {
179 Plane* p = card.get_plane(num);
180 if (!p)
181 EXIT("Bad plane id '%u'", num);
183 pinfo.plane = p;
184 } else {
185 auto planes = card.get_planes();
187 if (num >= planes.size())
188 EXIT("Bad plane number '%u'", num);
190 pinfo.plane = planes[num];
191 }
192 } else {
193 for (Plane* p : output.crtc->get_possible_planes()) {
194 if (s_used_planes.find(p) != s_used_planes.end())
195 continue;
197 if (p->plane_type() != PlaneType::Overlay)
198 continue;
200 pinfo.plane = p;
201 }
203 if (!pinfo.plane)
204 EXIT("Failed to find available plane");
205 }
207 s_used_planes.insert(pinfo.plane);
209 pinfo.w = stoul(sm[5]);
210 pinfo.h = stoul(sm[6]);
212 if (sm[3].matched)
213 pinfo.x = stoul(sm[3]);
214 else
215 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
217 if (sm[4].matched)
218 pinfo.y = stoul(sm[4]);
219 else
220 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
221 }
223 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
224 {
225 vector<DumbFramebuffer*> v;
227 for (unsigned i = 0; i < s_num_buffers; ++i)
228 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
230 return v;
231 }
233 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
234 {
235 unsigned w = def_w;
236 unsigned h = def_h;
237 PixelFormat format = PixelFormat::XRGB8888;
239 if (!fb_str.empty()) {
240 // XXX the regexp is not quite correct
241 // 400x400-NV12
242 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
244 smatch sm;
245 if (!regex_match(fb_str, sm, fb_re))
246 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
248 if (sm[1].matched)
249 w = stoul(sm[1]);
250 if (sm[2].matched)
251 h = stoul(sm[2]);
252 if (sm[3].matched)
253 format = FourCCToPixelFormat(sm[3]);
254 }
256 vector<DumbFramebuffer*> v;
258 for (unsigned i = 0; i < s_num_buffers; ++i)
259 v.push_back(new DumbFramebuffer(card, w, h, format));
261 return v;
262 }
264 static const char* usage_str =
265 "Usage: kmstest [OPTION]...\n\n"
266 "Show a test pattern on a display or plane\n\n"
267 "Options:\n"
268 " --device=DEVICE DEVICE is the path to DRM card to open\n"
269 " -c, --connector=CONN CONN is <connector>\n"
270 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
271 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
272 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
273 " --dmt Search for the given mode from DMT tables\n"
274 " --cea Search for the given mode from CEA tables\n"
275 " --flip Do page flipping for each output\n"
276 " --sync Synchronize page flipping\n"
277 "\n"
278 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
279 "<connector> can also be given by name.\n"
280 "\n"
281 "Options can be given multiple times to set up multiple displays or planes.\n"
282 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
283 "an earlier option.\n"
284 "If you omit parameters, kmstest tries to guess what you mean\n"
285 "\n"
286 "Examples:\n"
287 "\n"
288 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
289 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
290 "XR24 framebuffer on first connected connector in the default mode:\n"
291 " kmstest -f XR24\n\n"
292 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
293 " kmstest -p 400x400 -f XR24\n\n"
294 "Test pattern on the second connector with default mode:\n"
295 " kmstest -c 1\n"
296 ;
298 static void usage()
299 {
300 puts(usage_str);
301 }
303 enum class ObjectType
304 {
305 Connector,
306 Crtc,
307 Plane,
308 Framebuffer,
309 };
311 struct Arg
312 {
313 ObjectType type;
314 string arg;
315 };
317 static string s_device_path = "/dev/dri/card0";
319 static vector<Arg> parse_cmdline(int argc, char **argv)
320 {
321 vector<Arg> args;
323 OptionSet optionset = {
324 Option("|device=",
325 [&](string s)
326 {
327 s_device_path = s;
328 }),
329 Option("c|connector=",
330 [&](string s)
331 {
332 args.push_back(Arg { ObjectType::Connector, s });
333 }),
334 Option("r|crtc=", [&](string s)
335 {
336 args.push_back(Arg { ObjectType::Crtc, s });
337 }),
338 Option("p|plane=", [&](string s)
339 {
340 args.push_back(Arg { ObjectType::Plane, s });
341 }),
342 Option("f|fb=", [&](string s)
343 {
344 args.push_back(Arg { ObjectType::Framebuffer, s });
345 }),
346 Option("|dmt", []()
347 {
348 s_use_dmt = true;
349 }),
350 Option("|cea", []()
351 {
352 s_use_cea = true;
353 }),
354 Option("|flip", []()
355 {
356 s_flip_mode = true;
357 s_num_buffers = 2;
358 }),
359 Option("|sync", []()
360 {
361 s_flip_sync = true;
362 }),
363 Option("h|help", [&]()
364 {
365 usage();
366 exit(-1);
367 }),
368 };
370 optionset.parse(argc, argv);
372 if (optionset.params().size() > 0) {
373 usage();
374 exit(-1);
375 }
377 return args;
378 }
380 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
381 {
382 vector<OutputInfo> outputs;
384 if (output_args.size() == 0) {
385 // no output args, show a pattern on all screens
386 for (auto& pipe : card.get_connected_pipelines()) {
387 OutputInfo output = { };
388 output.connector = pipe.connector;
389 output.crtc = pipe.crtc;
390 output.mode = output.connector->get_default_mode();
392 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
394 outputs.push_back(output);
395 }
397 return outputs;
398 }
400 OutputInfo* current_output = 0;
401 PlaneInfo* current_plane = 0;
403 for (auto& arg : output_args) {
404 switch (arg.type) {
405 case ObjectType::Connector:
406 {
407 outputs.push_back(OutputInfo { });
408 current_output = &outputs.back();
410 parse_connector(card, arg.arg, *current_output);
411 current_plane = 0;
413 break;
414 }
416 case ObjectType::Crtc:
417 {
418 if (!current_output) {
419 outputs.push_back(OutputInfo { });
420 current_output = &outputs.back();
421 }
423 if (!current_output->connector)
424 get_default_connector(card, *current_output);
426 parse_crtc(card, arg.arg, *current_output);
428 current_output->user_set_crtc = true;
430 current_plane = 0;
432 break;
433 }
435 case ObjectType::Plane:
436 {
437 if (!current_output) {
438 outputs.push_back(OutputInfo { });
439 current_output = &outputs.back();
440 }
442 if (!current_output->connector)
443 get_default_connector(card, *current_output);
445 if (!current_output->crtc)
446 get_default_crtc(card, *current_output);
448 current_output->planes.push_back(PlaneInfo { });
449 current_plane = ¤t_output->planes.back();
451 parse_plane(card, arg.arg, *current_output, *current_plane);
453 break;
454 }
456 case ObjectType::Framebuffer:
457 {
458 if (!current_output) {
459 outputs.push_back(OutputInfo { });
460 current_output = &outputs.back();
461 }
463 if (!current_output->connector)
464 get_default_connector(card, *current_output);
466 if (!current_output->crtc)
467 get_default_crtc(card, *current_output);
469 int def_w, def_h;
471 if (current_plane) {
472 def_w = current_plane->w;
473 def_h = current_plane->h;
474 } else {
475 def_w = current_output->mode.hdisplay;
476 def_h = current_output->mode.vdisplay;
477 }
479 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
481 if (current_plane)
482 current_plane->fbs = fbs;
483 else
484 current_output->fbs = fbs;
486 break;
487 }
488 }
489 }
491 // create default framebuffers if needed
492 for (OutputInfo& o : outputs) {
493 if (!o.crtc) {
494 get_default_crtc(card, *current_output);
495 o.user_set_crtc = true;
496 }
498 if (o.fbs.empty() && o.user_set_crtc)
499 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
501 for (PlaneInfo &p : o.planes) {
502 if (p.fbs.empty())
503 p.fbs = get_default_fb(card, p.w, p.h);
504 }
505 }
507 return outputs;
508 }
510 static std::string videomode_to_string(const Videomode& m)
511 {
512 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
513 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
515 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
516 m.name.c_str(),
517 m.clock / 1000.0,
518 h.c_str(), v.c_str(),
519 m.vrefresh, m.calculated_vrefresh(),
520 m.flags,
521 m.type);
522 }
524 static void print_outputs(const vector<OutputInfo>& outputs)
525 {
526 for (unsigned i = 0; i < outputs.size(); ++i) {
527 const OutputInfo& o = outputs[i];
529 printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
530 o.connector->fullname().c_str());
531 printf(" Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
532 if (o.primary_plane)
533 printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
534 printf(": %s\n", videomode_to_string(o.mode).c_str());
535 if (!o.fbs.empty()) {
536 auto fb = o.fbs[0];
537 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
538 PixelFormatToFourCC(fb->format()).c_str());
539 }
541 for (unsigned j = 0; j < o.planes.size(); ++j) {
542 const PlaneInfo& p = o.planes[j];
543 auto fb = p.fbs[0];
544 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
545 p.x, p.y, p.w, p.h);
546 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
547 PixelFormatToFourCC(fb->format()).c_str());
548 }
549 }
550 }
552 static void draw_test_patterns(const vector<OutputInfo>& outputs)
553 {
554 for (const OutputInfo& o : outputs) {
555 for (auto fb : o.fbs)
556 draw_test_pattern(*fb);
558 for (const PlaneInfo& p : o.planes)
559 for (auto fb : p.fbs)
560 draw_test_pattern(*fb);
561 }
562 }
564 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
565 {
566 for (const OutputInfo& o : outputs) {
567 auto conn = o.connector;
568 auto crtc = o.crtc;
570 if (!o.fbs.empty()) {
571 auto fb = o.fbs[0];
572 int r = crtc->set_mode(conn, *fb, o.mode);
573 if (r)
574 printf("crtc->set_mode() failed for crtc %u: %s\n",
575 crtc->id(), strerror(-r));
576 }
578 for (const PlaneInfo& p : o.planes) {
579 auto fb = p.fbs[0];
580 int r = crtc->set_plane(p.plane, *fb,
581 p.x, p.y, p.w, p.h,
582 0, 0, fb->width(), fb->height());
583 if (r)
584 printf("crtc->set_plane() failed for plane %u: %s\n",
585 p.plane->id(), strerror(-r));
586 }
587 }
588 }
590 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
591 {
592 // Keep blobs here so that we keep ref to them until we have committed the req
593 vector<unique_ptr<Blob>> blobs;
595 AtomicReq req(card);
597 for (const OutputInfo& o : outputs) {
598 auto conn = o.connector;
599 auto crtc = o.crtc;
601 if (!o.fbs.empty()) {
602 auto fb = o.fbs[0];
604 blobs.emplace_back(o.mode.to_blob(card));
605 Blob* mode_blob = blobs.back().get();
607 req.add(conn, {
608 { "CRTC_ID", crtc->id() },
609 });
611 req.add(crtc, {
612 { "ACTIVE", 1 },
613 { "MODE_ID", mode_blob->id() },
614 });
616 req.add(o.primary_plane, {
617 { "FB_ID", fb->id() },
618 { "CRTC_ID", crtc->id() },
619 { "SRC_X", 0 << 16 },
620 { "SRC_Y", 0 << 16 },
621 { "SRC_W", fb->width() << 16 },
622 { "SRC_H", fb->height() << 16 },
623 { "CRTC_X", 0 },
624 { "CRTC_Y", 0 },
625 { "CRTC_W", fb->width() },
626 { "CRTC_H", fb->height() },
627 });
628 }
630 for (const PlaneInfo& p : o.planes) {
631 auto fb = p.fbs[0];
633 req.add(p.plane, {
634 { "FB_ID", fb->id() },
635 { "CRTC_ID", crtc->id() },
636 { "SRC_X", 0 << 16 },
637 { "SRC_Y", 0 << 16 },
638 { "SRC_W", fb->width() << 16 },
639 { "SRC_H", fb->height() << 16 },
640 { "CRTC_X", p.x },
641 { "CRTC_Y", p.y },
642 { "CRTC_W", p.w },
643 { "CRTC_H", p.h },
644 });
645 }
646 }
648 int r;
650 r = req.test(true);
651 if (r)
652 EXIT("Atomic test failed: %d\n", r);
654 r = req.commit_sync(true);
655 if (r)
656 EXIT("Atomic commit failed: %d\n", r);
657 }
659 class FlipState : private PageFlipHandlerBase
660 {
661 public:
662 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
663 : m_card(card), m_name(name), m_outputs(outputs)
664 {
665 }
667 void start_flipping()
668 {
669 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
670 m_slowest_frame = std::chrono::duration<float>::min();
671 m_frame_num = 0;
672 queue_next();
673 }
675 private:
676 void handle_page_flip(uint32_t frame, double time)
677 {
678 m_frame_num++;
680 auto now = std::chrono::steady_clock::now();
682 std::chrono::duration<float> diff = now - m_prev_frame;
683 if (diff > m_slowest_frame)
684 m_slowest_frame = diff;
686 if (m_frame_num % 100 == 0) {
687 std::chrono::duration<float> fsec = now - m_prev_print;
688 printf("Connector %s: fps %f, slowest %.2f ms\n",
689 m_name.c_str(),
690 100.0 / fsec.count(),
691 m_slowest_frame.count() * 1000);
692 m_prev_print = now;
693 m_slowest_frame = std::chrono::duration<float>::min();
694 }
696 m_prev_frame = now;
698 queue_next();
699 }
701 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
702 {
703 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
704 }
706 static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
707 {
708 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
709 int new_xpos = get_bar_pos(fb, frame_num);
711 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
712 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
713 }
715 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
716 {
717 unsigned cur = frame_num % s_num_buffers;
719 if (!o.fbs.empty()) {
720 auto fb = o.fbs[cur];
722 draw_bar(fb, frame_num);
724 req.add(o.primary_plane, {
725 { "FB_ID", fb->id() },
726 });
727 }
729 for (const PlaneInfo& p : o.planes) {
730 auto fb = p.fbs[cur];
732 draw_bar(fb, frame_num);
734 req.add(p.plane, {
735 { "FB_ID", fb->id() },
736 });
737 }
738 }
740 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
741 {
742 unsigned cur = frame_num % s_num_buffers;
744 if (!o.fbs.empty()) {
745 auto fb = o.fbs[cur];
747 draw_bar(fb, frame_num);
749 int r = o.crtc->page_flip(*fb, this);
750 ASSERT(r == 0);
751 }
753 for (const PlaneInfo& p : o.planes) {
754 auto fb = p.fbs[cur];
756 draw_bar(fb, frame_num);
758 int r = o.crtc->set_plane(p.plane, *fb,
759 p.x, p.y, p.w, p.h,
760 0, 0, fb->width(), fb->height());
761 ASSERT(r == 0);
762 }
763 }
765 void queue_next()
766 {
767 if (m_card.has_atomic()) {
768 AtomicReq req(m_card);
770 for (auto o : m_outputs)
771 do_flip_output(req, m_frame_num, *o);
773 int r = req.commit(this);
774 if (r)
775 EXIT("Flip commit failed: %d\n", r);
776 } else {
777 ASSERT(m_outputs.size() == 1);
778 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
779 }
780 }
782 Card& m_card;
783 string m_name;
784 vector<const OutputInfo*> m_outputs;
785 unsigned m_frame_num;
787 chrono::steady_clock::time_point m_prev_print;
788 chrono::steady_clock::time_point m_prev_frame;
789 chrono::duration<float> m_slowest_frame;
791 static const unsigned bar_width = 20;
792 static const unsigned bar_speed = 8;
793 };
795 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
796 {
797 fd_set fds;
799 FD_ZERO(&fds);
801 int fd = card.fd();
803 vector<unique_ptr<FlipState>> flipstates;
805 if (!s_flip_sync) {
806 for (const OutputInfo& o : outputs) {
807 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
808 flipstates.push_back(move(fs));
809 }
810 } else {
811 vector<const OutputInfo*> ois;
813 string name;
814 for (const OutputInfo& o : outputs) {
815 name += to_string(o.connector->idx()) + ",";
816 ois.push_back(&o);
817 }
819 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
820 flipstates.push_back(move(fs));
821 }
823 for (unique_ptr<FlipState>& fs : flipstates)
824 fs->start_flipping();
826 while (true) {
827 int r;
829 FD_SET(0, &fds);
830 FD_SET(fd, &fds);
832 r = select(fd + 1, &fds, NULL, NULL, NULL);
833 if (r < 0) {
834 fprintf(stderr, "select() failed with %d: %m\n", errno);
835 break;
836 } else if (FD_ISSET(0, &fds)) {
837 fprintf(stderr, "Exit due to user-input\n");
838 break;
839 } else if (FD_ISSET(fd, &fds)) {
840 card.call_page_flip_handlers();
841 }
842 }
843 }
845 int main(int argc, char **argv)
846 {
847 vector<Arg> output_args = parse_cmdline(argc, argv);
849 Card card(s_device_path);
851 if (!card.has_atomic() && s_flip_sync)
852 EXIT("Synchronized flipping requires atomic modesetting");
854 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
856 if (card.has_atomic()) {
857 for (OutputInfo& o : outputs) {
858 o.primary_plane = o.crtc->get_primary_plane();
860 if (!o.fbs.empty() && !o.primary_plane)
861 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
862 }
863 }
865 if (!s_flip_mode)
866 draw_test_patterns(outputs);
868 print_outputs(outputs);
870 if (card.has_atomic())
871 set_crtcs_n_planes(card, outputs);
872 else
873 set_crtcs_n_planes_legacy(card, outputs);
875 printf("press enter to exit\n");
877 if (s_flip_mode)
878 main_flip(card, outputs);
879 else
880 getchar();
881 }