1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
8 #include <kms++.h>
9 #include <modedb.h>
11 #include <kms++util.h>
12 #include <opts.h>
14 using namespace std;
15 using namespace kms;
17 struct PlaneInfo
18 {
19 Plane* plane;
21 unsigned x;
22 unsigned y;
23 unsigned w;
24 unsigned h;
26 vector<DumbFramebuffer*> fbs;
27 };
29 struct OutputInfo
30 {
31 Connector* connector;
33 Crtc* crtc;
34 Plane* primary_plane;
35 Videomode mode;
36 bool user_set_crtc;
37 vector<DumbFramebuffer*> fbs;
39 vector<PlaneInfo> planes;
40 };
42 static bool s_use_dmt;
43 static bool s_use_cea;
44 static unsigned s_num_buffers = 1;
45 static bool s_flip_mode;
46 static bool s_flip_sync;
48 static set<Crtc*> s_used_crtcs;
49 static set<Plane*> s_used_planes;
51 __attribute__ ((unused))
52 static void print_regex_match(smatch sm)
53 {
54 for (unsigned i = 0; i < sm.size(); ++i) {
55 string str = sm[i].str();
56 printf("%u: %s\n", i, str.c_str());
57 }
58 }
60 static void get_default_connector(Card& card, OutputInfo& output)
61 {
62 output.connector = card.get_first_connected_connector();
63 output.mode = output.connector->get_default_mode();
64 }
66 static void parse_connector(Card& card, const string& str, OutputInfo& output)
67 {
68 Connector* conn = resolve_connector(card, str);
70 if (!conn)
71 EXIT("No connector '%s'", str.c_str());
73 if (!conn->connected())
74 EXIT("Connector '%s' not connected", conn->fullname().c_str());
76 output.connector = conn;
77 output.mode = output.connector->get_default_mode();
78 }
80 static void get_default_crtc(Card& card, OutputInfo& output)
81 {
82 Crtc* crtc = output.connector->get_current_crtc();
84 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
85 s_used_crtcs.insert(crtc);
86 output.crtc = crtc;
87 return;
88 }
90 for (const auto& possible : output.connector->get_possible_crtcs()) {
91 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
92 s_used_crtcs.insert(possible);
93 output.crtc = possible;
94 return;
95 }
96 }
98 EXIT("Could not find available crtc");
99 }
101 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
102 {
103 // @12:1920x1200@60
104 const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
106 smatch sm;
107 if (!regex_match(crtc_str, sm, mode_re))
108 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
110 if (sm[2].matched) {
111 bool use_id = sm[1].length() == 1;
112 unsigned num = stoul(sm[2].str());
114 if (use_id) {
115 Crtc* c = card.get_crtc(num);
116 if (!c)
117 EXIT("Bad crtc id '%u'", num);
119 output.crtc = c;
120 } else {
121 auto crtcs = card.get_crtcs();
123 if (num >= crtcs.size())
124 EXIT("Bad crtc number '%u'", num);
126 output.crtc = crtcs[num];
127 }
128 } else {
129 output.crtc = output.connector->get_current_crtc();
130 }
132 unsigned w = stoul(sm[3]);
133 unsigned h = stoul(sm[4]);
134 bool ilace = sm[5].matched ? true : false;
135 unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
137 bool found_mode = false;
139 try {
140 output.mode = output.connector->get_mode(w, h, refresh, ilace);
141 found_mode = true;
142 } catch (exception& e) { }
144 if (!found_mode && s_use_dmt) {
145 try {
146 output.mode = find_dmt(w, h, refresh, ilace);
147 found_mode = true;
148 printf("Found mode from DMT\n");
149 } catch (exception& e) { }
150 }
152 if (!found_mode && s_use_cea) {
153 try {
154 output.mode = find_cea(w, h, refresh, ilace);
155 found_mode = true;
156 printf("Found mode from CEA\n");
157 } catch (exception& e) { }
158 }
160 if (!found_mode)
161 throw invalid_argument("Mode not found");
162 }
164 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
165 {
166 // 3:400,400-400x400
167 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
169 smatch sm;
170 if (!regex_match(plane_str, sm, plane_re))
171 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
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 Plane* p = card.get_plane(num);
179 if (!p)
180 EXIT("Bad plane id '%u'", num);
182 pinfo.plane = p;
183 } else {
184 auto planes = card.get_planes();
186 if (num >= planes.size())
187 EXIT("Bad plane number '%u'", num);
189 pinfo.plane = planes[num];
190 }
191 } else {
192 for (Plane* p : output.crtc->get_possible_planes()) {
193 if (s_used_planes.find(p) != s_used_planes.end())
194 continue;
196 if (p->plane_type() != PlaneType::Overlay)
197 continue;
199 pinfo.plane = p;
200 }
202 if (!pinfo.plane)
203 EXIT("Failed to find available plane");
204 }
206 s_used_planes.insert(pinfo.plane);
208 pinfo.w = stoul(sm[5]);
209 pinfo.h = stoul(sm[6]);
211 if (sm[3].matched)
212 pinfo.x = stoul(sm[3]);
213 else
214 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
216 if (sm[4].matched)
217 pinfo.y = stoul(sm[4]);
218 else
219 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
220 }
222 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
223 {
224 vector<DumbFramebuffer*> v;
226 for (unsigned i = 0; i < s_num_buffers; ++i)
227 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
229 return v;
230 }
232 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
233 {
234 unsigned w = def_w;
235 unsigned h = def_h;
236 PixelFormat format = PixelFormat::XRGB8888;
238 if (!fb_str.empty()) {
239 // XXX the regexp is not quite correct
240 // 400x400-NV12
241 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
243 smatch sm;
244 if (!regex_match(fb_str, sm, fb_re))
245 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
247 if (sm[1].matched)
248 w = stoul(sm[1]);
249 if (sm[2].matched)
250 h = stoul(sm[2]);
251 if (sm[3].matched)
252 format = FourCCToPixelFormat(sm[3]);
253 }
255 vector<DumbFramebuffer*> v;
257 for (unsigned i = 0; i < s_num_buffers; ++i)
258 v.push_back(new DumbFramebuffer(card, w, h, format));
260 return v;
261 }
263 static const char* usage_str =
264 "Usage: testpat [OPTION]...\n\n"
265 "Show a test pattern on a display or plane\n\n"
266 "Options:\n"
267 " --device=DEVICE DEVICE is the path to DRM card to open\n"
268 " -c, --connector=CONN CONN is <connector>\n"
269 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
270 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
271 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
272 " --dmt Search for the given mode from DMT tables\n"
273 " --cea Search for the given mode from CEA tables\n"
274 " --flip Do page flipping for each output\n"
275 " --sync Synchronize page flipping\n"
276 "\n"
277 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
278 "<connector> can also be given by name.\n"
279 "\n"
280 "Options can be given multiple times to set up multiple displays or planes.\n"
281 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
282 "an earlier option.\n"
283 "If you omit parameters, testpat tries to guess what you mean\n"
284 "\n"
285 "Examples:\n"
286 "\n"
287 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
288 " testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
289 "XR24 framebuffer on first connected connector in the default mode:\n"
290 " testpat -f XR24\n\n"
291 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
292 " testpat -p 400x400 -f XR24\n\n"
293 "Test pattern on the second connector with default mode:\n"
294 " testpat -c 1\n"
295 ;
297 static void usage()
298 {
299 puts(usage_str);
300 }
302 enum class ObjectType
303 {
304 Connector,
305 Crtc,
306 Plane,
307 Framebuffer,
308 };
310 struct Arg
311 {
312 ObjectType type;
313 string arg;
314 };
316 static string s_device_path = "/dev/dri/card0";
318 static vector<Arg> parse_cmdline(int argc, char **argv)
319 {
320 vector<Arg> args;
322 OptionSet optionset = {
323 Option("|device=",
324 [&](string s)
325 {
326 s_device_path = s;
327 }),
328 Option("c|connector=",
329 [&](string s)
330 {
331 args.push_back(Arg { ObjectType::Connector, s });
332 }),
333 Option("r|crtc=", [&](string s)
334 {
335 args.push_back(Arg { ObjectType::Crtc, s });
336 }),
337 Option("p|plane=", [&](string s)
338 {
339 args.push_back(Arg { ObjectType::Plane, s });
340 }),
341 Option("f|fb=", [&](string s)
342 {
343 args.push_back(Arg { ObjectType::Framebuffer, s });
344 }),
345 Option("|dmt", []()
346 {
347 s_use_dmt = true;
348 }),
349 Option("|cea", []()
350 {
351 s_use_cea = true;
352 }),
353 Option("|flip", []()
354 {
355 s_flip_mode = true;
356 s_num_buffers = 2;
357 }),
358 Option("|sync", []()
359 {
360 s_flip_sync = true;
361 }),
362 Option("h|help", [&]()
363 {
364 usage();
365 exit(-1);
366 }),
367 };
369 optionset.parse(argc, argv);
371 if (optionset.params().size() > 0) {
372 usage();
373 exit(-1);
374 }
376 return args;
377 }
379 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
380 {
381 vector<OutputInfo> outputs;
383 if (output_args.size() == 0) {
384 // no output args, show a pattern on all screens
385 for (auto& pipe : card.get_connected_pipelines()) {
386 OutputInfo output = { };
387 output.connector = pipe.connector;
388 output.crtc = pipe.crtc;
389 output.mode = output.connector->get_default_mode();
391 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
393 outputs.push_back(output);
394 }
396 return outputs;
397 }
399 OutputInfo* current_output = 0;
400 PlaneInfo* current_plane = 0;
402 for (auto& arg : output_args) {
403 switch (arg.type) {
404 case ObjectType::Connector:
405 {
406 outputs.push_back(OutputInfo { });
407 current_output = &outputs.back();
409 parse_connector(card, arg.arg, *current_output);
410 current_plane = 0;
412 break;
413 }
415 case ObjectType::Crtc:
416 {
417 if (!current_output) {
418 outputs.push_back(OutputInfo { });
419 current_output = &outputs.back();
420 }
422 if (!current_output->connector)
423 get_default_connector(card, *current_output);
425 parse_crtc(card, arg.arg, *current_output);
427 current_output->user_set_crtc = true;
429 current_plane = 0;
431 break;
432 }
434 case ObjectType::Plane:
435 {
436 if (!current_output) {
437 outputs.push_back(OutputInfo { });
438 current_output = &outputs.back();
439 }
441 if (!current_output->connector)
442 get_default_connector(card, *current_output);
444 if (!current_output->crtc)
445 get_default_crtc(card, *current_output);
447 current_output->planes.push_back(PlaneInfo { });
448 current_plane = ¤t_output->planes.back();
450 parse_plane(card, arg.arg, *current_output, *current_plane);
452 break;
453 }
455 case ObjectType::Framebuffer:
456 {
457 if (!current_output) {
458 outputs.push_back(OutputInfo { });
459 current_output = &outputs.back();
460 }
462 if (!current_output->connector)
463 get_default_connector(card, *current_output);
465 if (!current_output->crtc)
466 get_default_crtc(card, *current_output);
468 int def_w, def_h;
470 if (current_plane) {
471 def_w = current_plane->w;
472 def_h = current_plane->h;
473 } else {
474 def_w = current_output->mode.hdisplay;
475 def_h = current_output->mode.vdisplay;
476 }
478 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
480 if (current_plane)
481 current_plane->fbs = fbs;
482 else
483 current_output->fbs = fbs;
485 break;
486 }
487 }
488 }
490 // create default framebuffers if needed
491 for (OutputInfo& o : outputs) {
492 if (!o.crtc) {
493 get_default_crtc(card, *current_output);
494 o.user_set_crtc = true;
495 }
497 if (o.fbs.empty() && o.user_set_crtc)
498 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
500 for (PlaneInfo &p : o.planes) {
501 if (p.fbs.empty())
502 p.fbs = get_default_fb(card, p.w, p.h);
503 }
504 }
506 return outputs;
507 }
509 static std::string videomode_to_string(const Videomode& mode)
510 {
511 unsigned hfp = mode.hsync_start - mode.hdisplay;
512 unsigned hsw = mode.hsync_end - mode.hsync_start;
513 unsigned hbp = mode.htotal - mode.hsync_end;
515 unsigned vfp = mode.vsync_start - mode.vdisplay;
516 unsigned vsw = mode.vsync_end - mode.vsync_start;
517 unsigned vbp = mode.vtotal - mode.vsync_end;
519 float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
520 if (mode.flags & (1<<4)) // XXX interlace
521 hz *= 2;
523 char buf[256];
525 sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
526 mode.clock / 1000.0,
527 mode.hdisplay, hfp, hsw, hbp,
528 mode.vdisplay, vfp, vsw, vbp,
529 mode.vrefresh, hz);
531 return std::string(buf);
532 }
534 static void print_outputs(const vector<OutputInfo>& outputs)
535 {
536 for (unsigned i = 0; i < outputs.size(); ++i) {
537 const OutputInfo& o = outputs[i];
539 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
540 o.connector->fullname().c_str());
541 printf(" Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
542 if (o.primary_plane)
543 printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
544 printf(": %ux%u-%u (%s)\n",
545 o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
546 videomode_to_string(o.mode).c_str());
547 if (!o.fbs.empty()) {
548 auto fb = o.fbs[0];
549 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
550 PixelFormatToFourCC(fb->format()).c_str());
551 }
553 for (unsigned j = 0; j < o.planes.size(); ++j) {
554 const PlaneInfo& p = o.planes[j];
555 auto fb = p.fbs[0];
556 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
557 p.x, p.y, p.w, p.h);
558 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
559 PixelFormatToFourCC(fb->format()).c_str());
560 }
561 }
562 }
564 static void draw_test_patterns(const vector<OutputInfo>& outputs)
565 {
566 for (const OutputInfo& o : outputs) {
567 for (auto fb : o.fbs)
568 draw_test_pattern(*fb);
570 for (const PlaneInfo& p : o.planes)
571 for (auto fb : p.fbs)
572 draw_test_pattern(*fb);
573 }
574 }
576 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
577 {
578 for (const OutputInfo& o : outputs) {
579 auto conn = o.connector;
580 auto crtc = o.crtc;
582 if (!o.fbs.empty()) {
583 auto fb = o.fbs[0];
584 int r = crtc->set_mode(conn, *fb, o.mode);
585 if (r)
586 printf("crtc->set_mode() failed for crtc %u: %s\n",
587 crtc->id(), strerror(-r));
588 }
590 for (const PlaneInfo& p : o.planes) {
591 auto fb = p.fbs[0];
592 int r = crtc->set_plane(p.plane, *fb,
593 p.x, p.y, p.w, p.h,
594 0, 0, fb->width(), fb->height());
595 if (r)
596 printf("crtc->set_plane() failed for plane %u: %s\n",
597 p.plane->id(), strerror(-r));
598 }
599 }
600 }
602 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
603 {
604 // Keep blobs here so that we keep ref to them until we have committed the req
605 vector<unique_ptr<Blob>> blobs;
607 AtomicReq req(card);
609 for (const OutputInfo& o : outputs) {
610 auto conn = o.connector;
611 auto crtc = o.crtc;
613 if (!o.fbs.empty()) {
614 auto fb = o.fbs[0];
616 blobs.emplace_back(o.mode.to_blob(card));
617 Blob* mode_blob = blobs.back().get();
619 req.add(conn, {
620 { "CRTC_ID", crtc->id() },
621 });
623 req.add(crtc, {
624 { "ACTIVE", 1 },
625 { "MODE_ID", mode_blob->id() },
626 });
628 req.add(o.primary_plane, {
629 { "FB_ID", fb->id() },
630 { "CRTC_ID", crtc->id() },
631 { "SRC_X", 0 << 16 },
632 { "SRC_Y", 0 << 16 },
633 { "SRC_W", fb->width() << 16 },
634 { "SRC_H", fb->height() << 16 },
635 { "CRTC_X", 0 },
636 { "CRTC_Y", 0 },
637 { "CRTC_W", fb->width() },
638 { "CRTC_H", fb->height() },
639 });
640 }
642 for (const PlaneInfo& p : o.planes) {
643 auto fb = p.fbs[0];
645 req.add(p.plane, {
646 { "FB_ID", fb->id() },
647 { "CRTC_ID", crtc->id() },
648 { "SRC_X", 0 << 16 },
649 { "SRC_Y", 0 << 16 },
650 { "SRC_W", fb->width() << 16 },
651 { "SRC_H", fb->height() << 16 },
652 { "CRTC_X", p.x },
653 { "CRTC_Y", p.y },
654 { "CRTC_W", p.w },
655 { "CRTC_H", p.h },
656 });
657 }
658 }
660 int r;
662 r = req.test(true);
663 if (r)
664 EXIT("Atomic test failed: %d\n", r);
666 r = req.commit_sync(true);
667 if (r)
668 EXIT("Atomic commit failed: %d\n", r);
669 }
671 class FlipState : private PageFlipHandlerBase
672 {
673 public:
674 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
675 : m_card(card), m_name(name), m_outputs(outputs)
676 {
677 }
679 void start_flipping()
680 {
681 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
682 m_slowest_frame = std::chrono::duration<float>::min();
683 m_frame_num = 0;
684 queue_next();
685 }
687 private:
688 void handle_page_flip(uint32_t frame, double time)
689 {
690 m_frame_num++;
692 auto now = std::chrono::steady_clock::now();
694 std::chrono::duration<float> diff = now - m_prev_frame;
695 if (diff > m_slowest_frame)
696 m_slowest_frame = diff;
698 if (m_frame_num % 100 == 0) {
699 std::chrono::duration<float> fsec = now - m_prev_print;
700 printf("Connector %s: fps %f, slowest %.2f ms\n",
701 m_name.c_str(),
702 100.0 / fsec.count(),
703 m_slowest_frame.count() * 1000);
704 m_prev_print = now;
705 m_slowest_frame = std::chrono::duration<float>::min();
706 }
708 m_prev_frame = now;
710 queue_next();
711 }
713 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
714 {
715 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
716 }
718 static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
719 {
720 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
721 int new_xpos = get_bar_pos(fb, frame_num);
723 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
724 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
725 }
727 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
728 {
729 unsigned cur = frame_num % s_num_buffers;
731 if (!o.fbs.empty()) {
732 auto fb = o.fbs[cur];
734 draw_bar(fb, frame_num);
736 req.add(o.primary_plane, {
737 { "FB_ID", fb->id() },
738 });
739 }
741 for (const PlaneInfo& p : o.planes) {
742 auto fb = p.fbs[cur];
744 draw_bar(fb, frame_num);
746 req.add(p.plane, {
747 { "FB_ID", fb->id() },
748 });
749 }
750 }
752 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
753 {
754 unsigned cur = frame_num % s_num_buffers;
756 if (!o.fbs.empty()) {
757 auto fb = o.fbs[cur];
759 draw_bar(fb, frame_num);
761 int r = o.crtc->page_flip(*fb, this);
762 ASSERT(r == 0);
763 }
765 for (const PlaneInfo& p : o.planes) {
766 auto fb = p.fbs[cur];
768 draw_bar(fb, frame_num);
770 int r = o.crtc->set_plane(p.plane, *fb,
771 p.x, p.y, p.w, p.h,
772 0, 0, fb->width(), fb->height());
773 ASSERT(r == 0);
774 }
775 }
777 void queue_next()
778 {
779 if (m_card.has_atomic()) {
780 AtomicReq req(m_card);
782 for (auto o : m_outputs)
783 do_flip_output(req, m_frame_num, *o);
785 int r = req.commit(this);
786 if (r)
787 EXIT("Flip commit failed: %d\n", r);
788 } else {
789 ASSERT(m_outputs.size() == 1);
790 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
791 }
792 }
794 Card& m_card;
795 string m_name;
796 vector<const OutputInfo*> m_outputs;
797 unsigned m_frame_num;
799 chrono::steady_clock::time_point m_prev_print;
800 chrono::steady_clock::time_point m_prev_frame;
801 chrono::duration<float> m_slowest_frame;
803 static const unsigned bar_width = 20;
804 static const unsigned bar_speed = 8;
805 };
807 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
808 {
809 fd_set fds;
811 FD_ZERO(&fds);
813 int fd = card.fd();
815 vector<unique_ptr<FlipState>> flipstates;
817 if (!s_flip_sync) {
818 for (const OutputInfo& o : outputs) {
819 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
820 flipstates.push_back(move(fs));
821 }
822 } else {
823 vector<const OutputInfo*> ois;
825 string name;
826 for (const OutputInfo& o : outputs) {
827 name += to_string(o.connector->idx()) + ",";
828 ois.push_back(&o);
829 }
831 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
832 flipstates.push_back(move(fs));
833 }
835 for (unique_ptr<FlipState>& fs : flipstates)
836 fs->start_flipping();
838 while (true) {
839 int r;
841 FD_SET(0, &fds);
842 FD_SET(fd, &fds);
844 r = select(fd + 1, &fds, NULL, NULL, NULL);
845 if (r < 0) {
846 fprintf(stderr, "select() failed with %d: %m\n", errno);
847 break;
848 } else if (FD_ISSET(0, &fds)) {
849 fprintf(stderr, "Exit due to user-input\n");
850 break;
851 } else if (FD_ISSET(fd, &fds)) {
852 card.call_page_flip_handlers();
853 }
854 }
855 }
857 int main(int argc, char **argv)
858 {
859 vector<Arg> output_args = parse_cmdline(argc, argv);
861 Card card(s_device_path);
863 if (!card.has_atomic() && s_flip_sync)
864 EXIT("Synchronized flipping requires atomic modesetting");
866 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
868 if (card.has_atomic()) {
869 for (OutputInfo& o : outputs) {
870 o.primary_plane = o.crtc->get_primary_plane();
872 if (!o.fbs.empty() && !o.primary_plane)
873 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
874 }
875 }
877 if (!s_flip_mode)
878 draw_test_patterns(outputs);
880 print_outputs(outputs);
882 if (card.has_atomic())
883 set_crtcs_n_planes(card, outputs);
884 else
885 set_crtcs_n_planes_legacy(card, outputs);
887 printf("press enter to exit\n");
889 if (s_flip_mode)
890 main_flip(card, outputs);
891 else
892 getchar();
893 }