1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
8 #include <kms++/kms++.h>
9 #include <kms++/modedb.h>
11 #include <kms++util/kms++util.h>
13 using namespace std;
14 using namespace kms;
16 struct PlaneInfo
17 {
18 Plane* plane;
20 unsigned x;
21 unsigned y;
22 unsigned w;
23 unsigned h;
25 vector<DumbFramebuffer*> fbs;
26 };
28 struct OutputInfo
29 {
30 Connector* connector;
32 Crtc* crtc;
33 Plane* primary_plane;
34 Videomode mode;
35 bool user_set_crtc;
36 vector<DumbFramebuffer*> fbs;
38 vector<PlaneInfo> planes;
39 };
41 static bool s_use_dmt;
42 static bool s_use_cea;
43 static unsigned s_num_buffers = 1;
44 static bool s_flip_mode;
45 static bool s_flip_sync;
47 static set<Crtc*> s_used_crtcs;
48 static set<Plane*> s_used_planes;
50 __attribute__ ((unused))
51 static void print_regex_match(smatch sm)
52 {
53 for (unsigned i = 0; i < sm.size(); ++i) {
54 string str = sm[i].str();
55 printf("%u: %s\n", i, str.c_str());
56 }
57 }
59 static void get_default_connector(Card& card, OutputInfo& output)
60 {
61 output.connector = card.get_first_connected_connector();
62 output.mode = output.connector->get_default_mode();
63 }
65 static void parse_connector(Card& card, const string& str, OutputInfo& output)
66 {
67 Connector* conn = resolve_connector(card, str);
69 if (!conn)
70 EXIT("No connector '%s'", str.c_str());
72 if (!conn->connected())
73 EXIT("Connector '%s' not connected", conn->fullname().c_str());
75 output.connector = conn;
76 output.mode = output.connector->get_default_mode();
77 }
79 static void get_default_crtc(Card& card, OutputInfo& output)
80 {
81 Crtc* crtc = output.connector->get_current_crtc();
83 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
84 s_used_crtcs.insert(crtc);
85 output.crtc = crtc;
86 return;
87 }
89 for (const auto& possible : output.connector->get_possible_crtcs()) {
90 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
91 s_used_crtcs.insert(possible);
92 output.crtc = possible;
93 return;
94 }
95 }
97 EXIT("Could not find available crtc");
98 }
100 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
101 {
102 // @12:1920x1200@60
103 const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
105 smatch sm;
106 if (!regex_match(crtc_str, sm, mode_re))
107 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
109 if (sm[2].matched) {
110 bool use_id = sm[1].length() == 1;
111 unsigned num = stoul(sm[2].str());
113 if (use_id) {
114 Crtc* c = card.get_crtc(num);
115 if (!c)
116 EXIT("Bad crtc id '%u'", num);
118 output.crtc = c;
119 } else {
120 auto crtcs = card.get_crtcs();
122 if (num >= crtcs.size())
123 EXIT("Bad crtc number '%u'", num);
125 output.crtc = crtcs[num];
126 }
127 } else {
128 output.crtc = output.connector->get_current_crtc();
129 }
131 unsigned w = stoul(sm[3]);
132 unsigned h = stoul(sm[4]);
133 bool ilace = sm[5].matched ? true : false;
134 unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
136 bool found_mode = false;
138 try {
139 output.mode = output.connector->get_mode(w, h, refresh, ilace);
140 found_mode = true;
141 } catch (exception& e) { }
143 if (!found_mode && s_use_dmt) {
144 try {
145 output.mode = find_dmt(w, h, refresh, ilace);
146 found_mode = true;
147 printf("Found mode from DMT\n");
148 } catch (exception& e) { }
149 }
151 if (!found_mode && s_use_cea) {
152 try {
153 output.mode = find_cea(w, h, refresh, ilace);
154 found_mode = true;
155 printf("Found mode from CEA\n");
156 } catch (exception& e) { }
157 }
159 if (!found_mode)
160 throw invalid_argument("Mode not found");
161 }
163 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
164 {
165 // 3:400,400-400x400
166 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
168 smatch sm;
169 if (!regex_match(plane_str, sm, plane_re))
170 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
172 if (sm[2].matched) {
173 bool use_id = sm[1].length() == 1;
174 unsigned num = stoul(sm[2].str());
176 if (use_id) {
177 Plane* p = card.get_plane(num);
178 if (!p)
179 EXIT("Bad plane id '%u'", num);
181 pinfo.plane = p;
182 } else {
183 auto planes = card.get_planes();
185 if (num >= planes.size())
186 EXIT("Bad plane number '%u'", num);
188 pinfo.plane = planes[num];
189 }
190 } else {
191 for (Plane* p : output.crtc->get_possible_planes()) {
192 if (s_used_planes.find(p) != s_used_planes.end())
193 continue;
195 if (p->plane_type() != PlaneType::Overlay)
196 continue;
198 pinfo.plane = p;
199 }
201 if (!pinfo.plane)
202 EXIT("Failed to find available plane");
203 }
205 s_used_planes.insert(pinfo.plane);
207 pinfo.w = stoul(sm[5]);
208 pinfo.h = stoul(sm[6]);
210 if (sm[3].matched)
211 pinfo.x = stoul(sm[3]);
212 else
213 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
215 if (sm[4].matched)
216 pinfo.y = stoul(sm[4]);
217 else
218 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
219 }
221 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
222 {
223 vector<DumbFramebuffer*> v;
225 for (unsigned i = 0; i < s_num_buffers; ++i)
226 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
228 return v;
229 }
231 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
232 {
233 unsigned w = def_w;
234 unsigned h = def_h;
235 PixelFormat format = PixelFormat::XRGB8888;
237 if (!fb_str.empty()) {
238 // XXX the regexp is not quite correct
239 // 400x400-NV12
240 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
242 smatch sm;
243 if (!regex_match(fb_str, sm, fb_re))
244 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
246 if (sm[1].matched)
247 w = stoul(sm[1]);
248 if (sm[2].matched)
249 h = stoul(sm[2]);
250 if (sm[3].matched)
251 format = FourCCToPixelFormat(sm[3]);
252 }
254 vector<DumbFramebuffer*> v;
256 for (unsigned i = 0; i < s_num_buffers; ++i)
257 v.push_back(new DumbFramebuffer(card, w, h, format));
259 return v;
260 }
262 static const char* usage_str =
263 "Usage: testpat [OPTION]...\n\n"
264 "Show a test pattern on a display or plane\n\n"
265 "Options:\n"
266 " --device=DEVICE DEVICE is the path to DRM card to open\n"
267 " -c, --connector=CONN CONN is <connector>\n"
268 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
269 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
270 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
271 " --dmt Search for the given mode from DMT tables\n"
272 " --cea Search for the given mode from CEA tables\n"
273 " --flip Do page flipping for each output\n"
274 " --sync Synchronize page flipping\n"
275 "\n"
276 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
277 "<connector> can also be given by name.\n"
278 "\n"
279 "Options can be given multiple times to set up multiple displays or planes.\n"
280 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
281 "an earlier option.\n"
282 "If you omit parameters, testpat tries to guess what you mean\n"
283 "\n"
284 "Examples:\n"
285 "\n"
286 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
287 " testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
288 "XR24 framebuffer on first connected connector in the default mode:\n"
289 " testpat -f XR24\n\n"
290 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
291 " testpat -p 400x400 -f XR24\n\n"
292 "Test pattern on the second connector with default mode:\n"
293 " testpat -c 1\n"
294 ;
296 static void usage()
297 {
298 puts(usage_str);
299 }
301 enum class ObjectType
302 {
303 Connector,
304 Crtc,
305 Plane,
306 Framebuffer,
307 };
309 struct Arg
310 {
311 ObjectType type;
312 string arg;
313 };
315 static string s_device_path = "/dev/dri/card0";
317 static vector<Arg> parse_cmdline(int argc, char **argv)
318 {
319 vector<Arg> args;
321 OptionSet optionset = {
322 Option("|device=",
323 [&](string s)
324 {
325 s_device_path = s;
326 }),
327 Option("c|connector=",
328 [&](string s)
329 {
330 args.push_back(Arg { ObjectType::Connector, s });
331 }),
332 Option("r|crtc=", [&](string s)
333 {
334 args.push_back(Arg { ObjectType::Crtc, s });
335 }),
336 Option("p|plane=", [&](string s)
337 {
338 args.push_back(Arg { ObjectType::Plane, s });
339 }),
340 Option("f|fb=", [&](string s)
341 {
342 args.push_back(Arg { ObjectType::Framebuffer, s });
343 }),
344 Option("|dmt", []()
345 {
346 s_use_dmt = true;
347 }),
348 Option("|cea", []()
349 {
350 s_use_cea = true;
351 }),
352 Option("|flip", []()
353 {
354 s_flip_mode = true;
355 s_num_buffers = 2;
356 }),
357 Option("|sync", []()
358 {
359 s_flip_sync = true;
360 }),
361 Option("h|help", [&]()
362 {
363 usage();
364 exit(-1);
365 }),
366 };
368 optionset.parse(argc, argv);
370 if (optionset.params().size() > 0) {
371 usage();
372 exit(-1);
373 }
375 return args;
376 }
378 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
379 {
380 vector<OutputInfo> outputs;
382 if (output_args.size() == 0) {
383 // no output args, show a pattern on all screens
384 for (auto& pipe : card.get_connected_pipelines()) {
385 OutputInfo output = { };
386 output.connector = pipe.connector;
387 output.crtc = pipe.crtc;
388 output.mode = output.connector->get_default_mode();
390 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
392 outputs.push_back(output);
393 }
395 return outputs;
396 }
398 OutputInfo* current_output = 0;
399 PlaneInfo* current_plane = 0;
401 for (auto& arg : output_args) {
402 switch (arg.type) {
403 case ObjectType::Connector:
404 {
405 outputs.push_back(OutputInfo { });
406 current_output = &outputs.back();
408 parse_connector(card, arg.arg, *current_output);
409 current_plane = 0;
411 break;
412 }
414 case ObjectType::Crtc:
415 {
416 if (!current_output) {
417 outputs.push_back(OutputInfo { });
418 current_output = &outputs.back();
419 }
421 if (!current_output->connector)
422 get_default_connector(card, *current_output);
424 parse_crtc(card, arg.arg, *current_output);
426 current_output->user_set_crtc = true;
428 current_plane = 0;
430 break;
431 }
433 case ObjectType::Plane:
434 {
435 if (!current_output) {
436 outputs.push_back(OutputInfo { });
437 current_output = &outputs.back();
438 }
440 if (!current_output->connector)
441 get_default_connector(card, *current_output);
443 if (!current_output->crtc)
444 get_default_crtc(card, *current_output);
446 current_output->planes.push_back(PlaneInfo { });
447 current_plane = ¤t_output->planes.back();
449 parse_plane(card, arg.arg, *current_output, *current_plane);
451 break;
452 }
454 case ObjectType::Framebuffer:
455 {
456 if (!current_output) {
457 outputs.push_back(OutputInfo { });
458 current_output = &outputs.back();
459 }
461 if (!current_output->connector)
462 get_default_connector(card, *current_output);
464 if (!current_output->crtc)
465 get_default_crtc(card, *current_output);
467 int def_w, def_h;
469 if (current_plane) {
470 def_w = current_plane->w;
471 def_h = current_plane->h;
472 } else {
473 def_w = current_output->mode.hdisplay;
474 def_h = current_output->mode.vdisplay;
475 }
477 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
479 if (current_plane)
480 current_plane->fbs = fbs;
481 else
482 current_output->fbs = fbs;
484 break;
485 }
486 }
487 }
489 // create default framebuffers if needed
490 for (OutputInfo& o : outputs) {
491 if (!o.crtc) {
492 get_default_crtc(card, *current_output);
493 o.user_set_crtc = true;
494 }
496 if (o.fbs.empty() && o.user_set_crtc)
497 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
499 for (PlaneInfo &p : o.planes) {
500 if (p.fbs.empty())
501 p.fbs = get_default_fb(card, p.w, p.h);
502 }
503 }
505 return outputs;
506 }
508 static std::string videomode_to_string(const Videomode& mode)
509 {
510 unsigned hfp = mode.hsync_start - mode.hdisplay;
511 unsigned hsw = mode.hsync_end - mode.hsync_start;
512 unsigned hbp = mode.htotal - mode.hsync_end;
514 unsigned vfp = mode.vsync_start - mode.vdisplay;
515 unsigned vsw = mode.vsync_end - mode.vsync_start;
516 unsigned vbp = mode.vtotal - mode.vsync_end;
518 float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
519 if (mode.flags & (1<<4)) // XXX interlace
520 hz *= 2;
522 char buf[256];
524 sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
525 mode.clock / 1000.0,
526 mode.hdisplay, hfp, hsw, hbp,
527 mode.vdisplay, vfp, vsw, vbp,
528 mode.vrefresh, hz);
530 return std::string(buf);
531 }
533 static void print_outputs(const vector<OutputInfo>& outputs)
534 {
535 for (unsigned i = 0; i < outputs.size(); ++i) {
536 const OutputInfo& o = outputs[i];
538 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
539 o.connector->fullname().c_str());
540 printf(" Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
541 if (o.primary_plane)
542 printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
543 printf(": %ux%u-%u (%s)\n",
544 o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
545 videomode_to_string(o.mode).c_str());
546 if (!o.fbs.empty()) {
547 auto fb = o.fbs[0];
548 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
549 PixelFormatToFourCC(fb->format()).c_str());
550 }
552 for (unsigned j = 0; j < o.planes.size(); ++j) {
553 const PlaneInfo& p = o.planes[j];
554 auto fb = p.fbs[0];
555 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
556 p.x, p.y, p.w, p.h);
557 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
558 PixelFormatToFourCC(fb->format()).c_str());
559 }
560 }
561 }
563 static void draw_test_patterns(const vector<OutputInfo>& outputs)
564 {
565 for (const OutputInfo& o : outputs) {
566 for (auto fb : o.fbs)
567 draw_test_pattern(*fb);
569 for (const PlaneInfo& p : o.planes)
570 for (auto fb : p.fbs)
571 draw_test_pattern(*fb);
572 }
573 }
575 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
576 {
577 for (const OutputInfo& o : outputs) {
578 auto conn = o.connector;
579 auto crtc = o.crtc;
581 if (!o.fbs.empty()) {
582 auto fb = o.fbs[0];
583 int r = crtc->set_mode(conn, *fb, o.mode);
584 if (r)
585 printf("crtc->set_mode() failed for crtc %u: %s\n",
586 crtc->id(), strerror(-r));
587 }
589 for (const PlaneInfo& p : o.planes) {
590 auto fb = p.fbs[0];
591 int r = crtc->set_plane(p.plane, *fb,
592 p.x, p.y, p.w, p.h,
593 0, 0, fb->width(), fb->height());
594 if (r)
595 printf("crtc->set_plane() failed for plane %u: %s\n",
596 p.plane->id(), strerror(-r));
597 }
598 }
599 }
601 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
602 {
603 // Keep blobs here so that we keep ref to them until we have committed the req
604 vector<unique_ptr<Blob>> blobs;
606 AtomicReq req(card);
608 for (const OutputInfo& o : outputs) {
609 auto conn = o.connector;
610 auto crtc = o.crtc;
612 if (!o.fbs.empty()) {
613 auto fb = o.fbs[0];
615 blobs.emplace_back(o.mode.to_blob(card));
616 Blob* mode_blob = blobs.back().get();
618 req.add(conn, {
619 { "CRTC_ID", crtc->id() },
620 });
622 req.add(crtc, {
623 { "ACTIVE", 1 },
624 { "MODE_ID", mode_blob->id() },
625 });
627 req.add(o.primary_plane, {
628 { "FB_ID", fb->id() },
629 { "CRTC_ID", crtc->id() },
630 { "SRC_X", 0 << 16 },
631 { "SRC_Y", 0 << 16 },
632 { "SRC_W", fb->width() << 16 },
633 { "SRC_H", fb->height() << 16 },
634 { "CRTC_X", 0 },
635 { "CRTC_Y", 0 },
636 { "CRTC_W", fb->width() },
637 { "CRTC_H", fb->height() },
638 });
639 }
641 for (const PlaneInfo& p : o.planes) {
642 auto fb = p.fbs[0];
644 req.add(p.plane, {
645 { "FB_ID", fb->id() },
646 { "CRTC_ID", crtc->id() },
647 { "SRC_X", 0 << 16 },
648 { "SRC_Y", 0 << 16 },
649 { "SRC_W", fb->width() << 16 },
650 { "SRC_H", fb->height() << 16 },
651 { "CRTC_X", p.x },
652 { "CRTC_Y", p.y },
653 { "CRTC_W", p.w },
654 { "CRTC_H", p.h },
655 });
656 }
657 }
659 int r;
661 r = req.test(true);
662 if (r)
663 EXIT("Atomic test failed: %d\n", r);
665 r = req.commit_sync(true);
666 if (r)
667 EXIT("Atomic commit failed: %d\n", r);
668 }
670 class FlipState : private PageFlipHandlerBase
671 {
672 public:
673 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
674 : m_card(card), m_name(name), m_outputs(outputs)
675 {
676 }
678 void start_flipping()
679 {
680 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
681 m_slowest_frame = std::chrono::duration<float>::min();
682 m_frame_num = 0;
683 queue_next();
684 }
686 private:
687 void handle_page_flip(uint32_t frame, double time)
688 {
689 m_frame_num++;
691 auto now = std::chrono::steady_clock::now();
693 std::chrono::duration<float> diff = now - m_prev_frame;
694 if (diff > m_slowest_frame)
695 m_slowest_frame = diff;
697 if (m_frame_num % 100 == 0) {
698 std::chrono::duration<float> fsec = now - m_prev_print;
699 printf("Connector %s: fps %f, slowest %.2f ms\n",
700 m_name.c_str(),
701 100.0 / fsec.count(),
702 m_slowest_frame.count() * 1000);
703 m_prev_print = now;
704 m_slowest_frame = std::chrono::duration<float>::min();
705 }
707 m_prev_frame = now;
709 queue_next();
710 }
712 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
713 {
714 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
715 }
717 static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
718 {
719 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
720 int new_xpos = get_bar_pos(fb, frame_num);
722 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
723 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
724 }
726 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
727 {
728 unsigned cur = frame_num % s_num_buffers;
730 if (!o.fbs.empty()) {
731 auto fb = o.fbs[cur];
733 draw_bar(fb, frame_num);
735 req.add(o.primary_plane, {
736 { "FB_ID", fb->id() },
737 });
738 }
740 for (const PlaneInfo& p : o.planes) {
741 auto fb = p.fbs[cur];
743 draw_bar(fb, frame_num);
745 req.add(p.plane, {
746 { "FB_ID", fb->id() },
747 });
748 }
749 }
751 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
752 {
753 unsigned cur = frame_num % s_num_buffers;
755 if (!o.fbs.empty()) {
756 auto fb = o.fbs[cur];
758 draw_bar(fb, frame_num);
760 int r = o.crtc->page_flip(*fb, this);
761 ASSERT(r == 0);
762 }
764 for (const PlaneInfo& p : o.planes) {
765 auto fb = p.fbs[cur];
767 draw_bar(fb, frame_num);
769 int r = o.crtc->set_plane(p.plane, *fb,
770 p.x, p.y, p.w, p.h,
771 0, 0, fb->width(), fb->height());
772 ASSERT(r == 0);
773 }
774 }
776 void queue_next()
777 {
778 if (m_card.has_atomic()) {
779 AtomicReq req(m_card);
781 for (auto o : m_outputs)
782 do_flip_output(req, m_frame_num, *o);
784 int r = req.commit(this);
785 if (r)
786 EXIT("Flip commit failed: %d\n", r);
787 } else {
788 ASSERT(m_outputs.size() == 1);
789 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
790 }
791 }
793 Card& m_card;
794 string m_name;
795 vector<const OutputInfo*> m_outputs;
796 unsigned m_frame_num;
798 chrono::steady_clock::time_point m_prev_print;
799 chrono::steady_clock::time_point m_prev_frame;
800 chrono::duration<float> m_slowest_frame;
802 static const unsigned bar_width = 20;
803 static const unsigned bar_speed = 8;
804 };
806 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
807 {
808 fd_set fds;
810 FD_ZERO(&fds);
812 int fd = card.fd();
814 vector<unique_ptr<FlipState>> flipstates;
816 if (!s_flip_sync) {
817 for (const OutputInfo& o : outputs) {
818 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
819 flipstates.push_back(move(fs));
820 }
821 } else {
822 vector<const OutputInfo*> ois;
824 string name;
825 for (const OutputInfo& o : outputs) {
826 name += to_string(o.connector->idx()) + ",";
827 ois.push_back(&o);
828 }
830 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
831 flipstates.push_back(move(fs));
832 }
834 for (unique_ptr<FlipState>& fs : flipstates)
835 fs->start_flipping();
837 while (true) {
838 int r;
840 FD_SET(0, &fds);
841 FD_SET(fd, &fds);
843 r = select(fd + 1, &fds, NULL, NULL, NULL);
844 if (r < 0) {
845 fprintf(stderr, "select() failed with %d: %m\n", errno);
846 break;
847 } else if (FD_ISSET(0, &fds)) {
848 fprintf(stderr, "Exit due to user-input\n");
849 break;
850 } else if (FD_ISSET(fd, &fds)) {
851 card.call_page_flip_handlers();
852 }
853 }
854 }
856 int main(int argc, char **argv)
857 {
858 vector<Arg> output_args = parse_cmdline(argc, argv);
860 Card card(s_device_path);
862 if (!card.has_atomic() && s_flip_sync)
863 EXIT("Synchronized flipping requires atomic modesetting");
865 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
867 if (card.has_atomic()) {
868 for (OutputInfo& o : outputs) {
869 o.primary_plane = o.crtc->get_primary_plane();
871 if (!o.fbs.empty() && !o.primary_plane)
872 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
873 }
874 }
876 if (!s_flip_mode)
877 draw_test_patterns(outputs);
879 print_outputs(outputs);
881 if (card.has_atomic())
882 set_crtcs_n_planes(card, outputs);
883 else
884 set_crtcs_n_planes_legacy(card, outputs);
886 printf("press enter to exit\n");
888 if (s_flip_mode)
889 main_flip(card, outputs);
890 else
891 getchar();
892 }