aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'utils/kmstest.cpp')
-rw-r--r--utils/kmstest.cpp1108
1 files changed, 1108 insertions, 0 deletions
diff --git a/utils/kmstest.cpp b/utils/kmstest.cpp
new file mode 100644
index 0000000..62b103f
--- /dev/null
+++ b/utils/kmstest.cpp
@@ -0,0 +1,1108 @@
1#include <cstdio>
2#include <cstring>
3#include <algorithm>
4#include <regex>
5#include <set>
6#include <chrono>
7#include <cstdint>
8#include <cinttypes>
9
10#include <sys/select.h>
11
12#include <kms++/kms++.h>
13#include <kms++/modedb.h>
14#include <kms++/mode_cvt.h>
15
16#include <kms++util/kms++util.h>
17
18using namespace std;
19using namespace kms;
20
21struct PropInfo {
22 PropInfo(string n, uint64_t v) : prop(NULL), name(n), val(v) {}
23
24 Property *prop;
25 string name;
26 uint64_t val;
27};
28
29struct PlaneInfo
30{
31 Plane* plane;
32
33 unsigned x;
34 unsigned y;
35 unsigned w;
36 unsigned h;
37
38 unsigned view_x;
39 unsigned view_y;
40 unsigned view_w;
41 unsigned view_h;
42
43 vector<Framebuffer*> fbs;
44
45 vector<PropInfo> props;
46};
47
48struct OutputInfo
49{
50 Connector* connector;
51
52 Crtc* crtc;
53 Videomode mode;
54 vector<Framebuffer*> legacy_fbs;
55
56 vector<PlaneInfo> planes;
57
58 vector<PropInfo> conn_props;
59 vector<PropInfo> crtc_props;
60};
61
62static bool s_use_dmt;
63static bool s_use_cea;
64static unsigned s_num_buffers = 1;
65static bool s_flip_mode;
66static bool s_flip_sync;
67static bool s_cvt;
68static bool s_cvt_v2;
69static bool s_cvt_vid_opt;
70static unsigned s_max_flips;
71
72__attribute__ ((unused))
73static void print_regex_match(smatch sm)
74{
75 for (unsigned i = 0; i < sm.size(); ++i) {
76 string str = sm[i].str();
77 printf("%u: %s\n", i, str.c_str());
78 }
79}
80
81static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
82{
83 Connector* conn = resman.reserve_connector(str);
84
85 if (!conn)
86 EXIT("No connector '%s'", str.c_str());
87
88 if (!conn->connected())
89 EXIT("Connector '%s' not connected", conn->fullname().c_str());
90
91 output.connector = conn;
92 output.mode = output.connector->get_default_mode();
93}
94
95static void get_default_crtc(ResourceManager& resman, OutputInfo& output)
96{
97 output.crtc = resman.reserve_crtc(output.connector);
98
99 if (!output.crtc)
100 EXIT("Could not find available crtc");
101}
102
103
104static PlaneInfo *add_default_planeinfo(OutputInfo* output)
105{
106 output->planes.push_back(PlaneInfo { });
107 PlaneInfo *ret = &output->planes.back();
108 ret->w = output->mode.hdisplay;
109 ret->h = output->mode.vdisplay;
110 return ret;
111}
112
113static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output)
114{
115 // @12:1920x1200i@60
116 // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
117
118 const regex modename_re("(?:(@?)(\\d+):)?" // @12:
119 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
120 "(?:@([\\d\\.]+))?"); // @60
121
122 const regex modeline_re("(?:(@?)(\\d+):)?" // @12:
123 "(\\d+)," // 33000000,
124 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])," // 800/210/30/16/-,
125 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])" // 480/22/13/10/-
126 "(?:,([i]+))?" // ,i
127 );
128
129 smatch sm;
130 if (regex_match(crtc_str, sm, modename_re)) {
131 if (sm[2].matched) {
132 bool use_id = sm[1].length() == 1;
133 unsigned num = stoul(sm[2].str());
134
135 if (use_id) {
136 Crtc* c = card.get_crtc(num);
137 if (!c)
138 EXIT("Bad crtc id '%u'", num);
139
140 output.crtc = c;
141 } else {
142 auto crtcs = card.get_crtcs();
143
144 if (num >= crtcs.size())
145 EXIT("Bad crtc number '%u'", num);
146
147 output.crtc = crtcs[num];
148 }
149 } else {
150 output.crtc = output.connector->get_current_crtc();
151 }
152
153 unsigned w = stoul(sm[3]);
154 unsigned h = stoul(sm[4]);
155 bool ilace = sm[5].matched ? true : false;
156 float refresh = sm[6].matched ? stof(sm[6]) : 0;
157
158 if (s_cvt) {
159 output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
160 } else if (s_use_dmt) {
161 try {
162 output.mode = find_dmt(w, h, refresh, ilace);
163 } catch (exception& e) {
164 EXIT("Mode not found from DMT tables\n");
165 }
166 } else if (s_use_cea) {
167 try {
168 output.mode = find_cea(w, h, refresh, ilace);
169 } catch (exception& e) {
170 EXIT("Mode not found from CEA tables\n");
171 }
172 } else {
173 try {
174 output.mode = output.connector->get_mode(w, h, refresh, ilace);
175 } catch (exception& e) {
176 EXIT("Mode not found from the connector\n");
177 }
178 }
179 } else if (regex_match(crtc_str, sm, modeline_re)) {
180 if (sm[2].matched) {
181 bool use_id = sm[1].length() == 1;
182 unsigned num = stoul(sm[2].str());
183
184 if (use_id) {
185 Crtc* c = card.get_crtc(num);
186 if (!c)
187 EXIT("Bad crtc id '%u'", num);
188
189 output.crtc = c;
190 } else {
191 auto crtcs = card.get_crtcs();
192
193 if (num >= crtcs.size())
194 EXIT("Bad crtc number '%u'", num);
195
196 output.crtc = crtcs[num];
197 }
198 } else {
199 output.crtc = output.connector->get_current_crtc();
200 }
201
202 unsigned clock = stoul(sm[3]);
203
204 unsigned hact = stoul(sm[4]);
205 unsigned hfp = stoul(sm[5]);
206 unsigned hsw = stoul(sm[6]);
207 unsigned hbp = stoul(sm[7]);
208 bool h_pos_sync = sm[8] == "+" ? true : false;
209
210 unsigned vact = stoul(sm[9]);
211 unsigned vfp = stoul(sm[10]);
212 unsigned vsw = stoul(sm[11]);
213 unsigned vbp = stoul(sm[12]);
214 bool v_pos_sync = sm[13] == "+" ? true : false;
215
216 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
217 output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
218 output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
219
220 if (sm[14].matched) {
221 for (int i = 0; i < sm[14].length(); ++i) {
222 char f = string(sm[14])[i];
223
224 switch (f) {
225 case 'i':
226 output.mode.set_interlace(true);
227 break;
228 default:
229 EXIT("Bad mode flag %c", f);
230 }
231 }
232 }
233 } else {
234 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
235 }
236
237 if (!resman.reserve_crtc(output.crtc))
238 EXIT("Could not find available crtc");
239}
240
241static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
242{
243 // 3:400,400-400x400
244 const regex plane_re("(?:(@?)(\\d+):)?" // 3:
245 "(?:(\\d+),(\\d+)-)?" // 400,400-
246 "(\\d+)x(\\d+)"); // 400x400
247
248 smatch sm;
249 if (!regex_match(plane_str, sm, plane_re))
250 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
251
252 if (sm[2].matched) {
253 bool use_id = sm[1].length() == 1;
254 unsigned num = stoul(sm[2].str());
255
256 if (use_id) {
257 Plane* p = card.get_plane(num);
258 if (!p)
259 EXIT("Bad plane id '%u'", num);
260
261 pinfo.plane = p;
262 } else {
263 auto planes = card.get_planes();
264
265 if (num >= planes.size())
266 EXIT("Bad plane number '%u'", num);
267
268 pinfo.plane = planes[num];
269 }
270
271 auto plane = resman.reserve_plane(pinfo.plane);
272 if (!plane)
273 EXIT("Plane id %u is not available", pinfo.plane->id());
274 }
275
276 pinfo.w = stoul(sm[5]);
277 pinfo.h = stoul(sm[6]);
278
279 if (sm[3].matched)
280 pinfo.x = stoul(sm[3]);
281 else
282 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
283
284 if (sm[4].matched)
285 pinfo.y = stoul(sm[4]);
286 else
287 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
288}
289
290static void parse_prop(const string& prop_str, vector<PropInfo> &props)
291{
292 string name, val;
293
294 size_t split = prop_str.find("=");
295
296 if (split == string::npos)
297 EXIT("Equal sign ('=') not found in %s", prop_str.c_str());
298
299 name = prop_str.substr(0, split);
300 val = prop_str.substr(split+1);
301
302 props.push_back(PropInfo(name, stoull(val, 0, 0)));
303}
304
305static void get_props(Card& card, vector<PropInfo> &props, const DrmPropObject* propobj)
306{
307 for (auto& pi : props)
308 pi.prop = propobj->get_prop(pi.name);
309}
310
311static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
312{
313 vector<Framebuffer*> v;
314
315 for (unsigned i = 0; i < s_num_buffers; ++i)
316 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
317
318 return v;
319}
320
321static void parse_fb(Card& card, const string& fb_str, OutputInfo* output, PlaneInfo* pinfo)
322{
323 unsigned w, h;
324 PixelFormat format = PixelFormat::XRGB8888;
325
326 if (pinfo) {
327 w = pinfo->w;
328 h = pinfo->h;
329 } else {
330 w = output->mode.hdisplay;
331 h = output->mode.vdisplay;
332 }
333
334 if (!fb_str.empty()) {
335 // XXX the regexp is not quite correct
336 // 400x400-NV12
337 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400
338 "(?:-)?" // -
339 "(\\w\\w\\w\\w)?"); // NV12
340
341 smatch sm;
342 if (!regex_match(fb_str, sm, fb_re))
343 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
344
345 if (sm[1].matched)
346 w = stoul(sm[1]);
347 if (sm[2].matched)
348 h = stoul(sm[2]);
349 if (sm[3].matched)
350 format = FourCCToPixelFormat(sm[3]);
351 }
352
353 vector<Framebuffer*> v;
354
355 for (unsigned i = 0; i < s_num_buffers; ++i)
356 v.push_back(new DumbFramebuffer(card, w, h, format));
357
358 if (pinfo)
359 pinfo->fbs = v;
360 else
361 output->legacy_fbs = v;
362}
363
364static void parse_view(const string& view_str, PlaneInfo& pinfo)
365{
366 const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)"); // 400,400-400x400
367
368 smatch sm;
369 if (!regex_match(view_str, sm, view_re))
370 EXIT("Failed to parse view option '%s'", view_str.c_str());
371
372 pinfo.view_x = stoul(sm[1]);
373 pinfo.view_y = stoul(sm[2]);
374 pinfo.view_w = stoul(sm[3]);
375 pinfo.view_h = stoul(sm[4]);
376}
377
378static const char* usage_str =
379 "Usage: kmstest [OPTION]...\n\n"
380 "Show a test pattern on a display or plane\n\n"
381 "Options:\n"
382 " --device=DEVICE DEVICE is the path to DRM card to open\n"
383 " -c, --connector=CONN CONN is <connector>\n"
384 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
385 " or\n"
386 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
387 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
388 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
389 " -v, --view=VIEW VIEW is <x>,<y>-<w>x<h>\n"
390 " -P, --property=PROP=VAL Set PROP to VAL in the previous DRM object\n"
391 " --dmt Search for the given mode from DMT tables\n"
392 " --cea Search for the given mode from CEA tables\n"
393 " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
394 " --flip[=max] Do page flipping for each output with an optional maximum flips count\n"
395 " --sync Synchronize page flipping\n"
396 "\n"
397 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n"
398 "<connector> can also be given by name.\n"
399 "\n"
400 "Options can be given multiple times to set up multiple displays or planes.\n"
401 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
402 "an earlier option.\n"
403 "If you omit parameters, kmstest tries to guess what you mean\n"
404 "\n"
405 "Examples:\n"
406 "\n"
407 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
408 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
409 "XR24 framebuffer on first connected connector in the default mode:\n"
410 " kmstest -f XR24\n\n"
411 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
412 " kmstest -p 400x400 -f XR24\n\n"
413 "Test pattern on the second connector with default mode:\n"
414 " kmstest -c 1\n"
415 "\n"
416 "Environmental variables:\n"
417 " KMSXX_DISABLE_UNIVERSAL_PLANES Don't enable universal planes even if available\n"
418 " KMSXX_DISABLE_ATOMIC Don't enable atomic modesetting even if available\n"
419 ;
420
421static void usage()
422{
423 puts(usage_str);
424}
425
426enum class ArgType
427{
428 Connector,
429 Crtc,
430 Plane,
431 Framebuffer,
432 View,
433 Property,
434};
435
436struct Arg
437{
438 ArgType type;
439 string arg;
440};
441
442static string s_device_path = "/dev/dri/card0";
443
444static vector<Arg> parse_cmdline(int argc, char **argv)
445{
446 vector<Arg> args;
447
448 OptionSet optionset = {
449 Option("|device=",
450 [&](string s)
451 {
452 s_device_path = s;
453 }),
454 Option("c|connector=",
455 [&](string s)
456 {
457 args.push_back(Arg { ArgType::Connector, s });
458 }),
459 Option("r|crtc=", [&](string s)
460 {
461 args.push_back(Arg { ArgType::Crtc, s });
462 }),
463 Option("p|plane=", [&](string s)
464 {
465 args.push_back(Arg { ArgType::Plane, s });
466 }),
467 Option("f|fb=", [&](string s)
468 {
469 args.push_back(Arg { ArgType::Framebuffer, s });
470 }),
471 Option("v|view=", [&](string s)
472 {
473 args.push_back(Arg { ArgType::View, s });
474 }),
475 Option("P|property=", [&](string s)
476 {
477 args.push_back(Arg { ArgType::Property, s });
478 }),
479 Option("|dmt", []()
480 {
481 s_use_dmt = true;
482 }),
483 Option("|cea", []()
484 {
485 s_use_cea = true;
486 }),
487 Option("|flip?", [&](string s)
488 {
489 s_flip_mode = true;
490 s_num_buffers = 2;
491 if (!s.empty())
492 s_max_flips = stoi(s);
493 }),
494 Option("|sync", []()
495 {
496 s_flip_sync = true;
497 }),
498 Option("|cvt=", [&](string s)
499 {
500 if (s == "v1")
501 s_cvt = true;
502 else if (s == "v2")
503 s_cvt = s_cvt_v2 = true;
504 else if (s == "v2o")
505 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
506 else {
507 usage();
508 exit(-1);
509 }
510 }),
511 Option("h|help", [&]()
512 {
513 usage();
514 exit(-1);
515 }),
516 };
517
518 optionset.parse(argc, argv);
519
520 if (optionset.params().size() > 0) {
521 usage();
522 exit(-1);
523 }
524
525 return args;
526}
527
528static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
529{
530 vector<OutputInfo> outputs;
531
532 OutputInfo* current_output = 0;
533 PlaneInfo* current_plane = 0;
534
535 for (auto& arg : output_args) {
536 switch (arg.type) {
537 case ArgType::Connector:
538 {
539 outputs.push_back(OutputInfo { });
540 current_output = &outputs.back();
541
542 get_connector(resman, *current_output, arg.arg);
543 current_plane = 0;
544
545 break;
546 }
547
548 case ArgType::Crtc:
549 {
550 if (!current_output) {
551 outputs.push_back(OutputInfo { });
552 current_output = &outputs.back();
553 }
554
555 if (!current_output->connector)
556 get_connector(resman, *current_output);
557
558 parse_crtc(resman, card, arg.arg, *current_output);
559
560 current_plane = 0;
561
562 break;
563 }
564
565 case ArgType::Plane:
566 {
567 if (!current_output) {
568 outputs.push_back(OutputInfo { });
569 current_output = &outputs.back();
570 }
571
572 if (!current_output->connector)
573 get_connector(resman, *current_output);
574
575 if (!current_output->crtc)
576 get_default_crtc(resman, *current_output);
577
578 current_plane = add_default_planeinfo(current_output);
579
580 parse_plane(resman, card, arg.arg, *current_output, *current_plane);
581
582 break;
583 }
584
585 case ArgType::Framebuffer:
586 {
587 if (!current_output) {
588 outputs.push_back(OutputInfo { });
589 current_output = &outputs.back();
590 }
591
592 if (!current_output->connector)
593 get_connector(resman, *current_output);
594
595 if (!current_output->crtc)
596 get_default_crtc(resman, *current_output);
597
598 if (!current_plane && card.has_atomic())
599 current_plane = add_default_planeinfo(current_output);
600
601 parse_fb(card, arg.arg, current_output, current_plane);
602
603 break;
604 }
605
606 case ArgType::View:
607 {
608 if (!current_plane || current_plane->fbs.empty())
609 EXIT("'view' parameter requires a plane and a fb");
610
611 parse_view(arg.arg, *current_plane);
612 break;
613 }
614
615 case ArgType::Property:
616 {
617 if (!current_output)
618 EXIT("No object to which set the property");
619
620 if (current_plane)
621 parse_prop(arg.arg, current_plane->props);
622 else if (current_output->crtc)
623 parse_prop(arg.arg, current_output->crtc_props);
624 else if (current_output->connector)
625 parse_prop(arg.arg, current_output->conn_props);
626 else
627 EXIT("no object");
628
629 break;
630 }
631 }
632 }
633
634 if (outputs.empty()) {
635 // no outputs defined, show a pattern on all screens
636 for (Connector* conn : card.get_connectors()) {
637 if (!conn->connected())
638 continue;
639
640 OutputInfo output = { };
641 output.connector = resman.reserve_connector(conn);
642 EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str());
643 output.crtc = resman.reserve_crtc(conn);
644 EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str());
645 output.mode = output.connector->get_default_mode();
646
647 outputs.push_back(output);
648 }
649 }
650
651 for (OutputInfo& o : outputs) {
652 get_props(card, o.conn_props, o.connector);
653
654 if (!o.crtc)
655 get_default_crtc(resman, o);
656
657 get_props(card, o.crtc_props, o.crtc);
658
659 if (card.has_atomic()) {
660 if (o.planes.empty())
661 add_default_planeinfo(&o);
662 } else {
663 if (o.legacy_fbs.empty())
664 o.legacy_fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
665 }
666
667 for (PlaneInfo &p : o.planes) {
668 if (p.fbs.empty())
669 p.fbs = get_default_fb(card, p.w, p.h);
670 }
671
672 for (PlaneInfo& p : o.planes) {
673 if (!p.plane) {
674 if (card.has_atomic())
675 p.plane = resman.reserve_generic_plane(o.crtc, p.fbs[0]->format());
676 else
677 p.plane = resman.reserve_overlay_plane(o.crtc, p.fbs[0]->format());
678
679 if (!p.plane)
680 EXIT("Failed to find available plane");
681 }
682 get_props(card, p.props, p.plane);
683 }
684 }
685
686 return outputs;
687}
688
689static std::string videomode_to_string(const Videomode& m)
690{
691 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
692 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
693
694 return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
695 m.name.c_str(),
696 m.clock / 1000.0,
697 h.c_str(), v.c_str(),
698 m.vrefresh, m.calculated_vrefresh(),
699 m.flags,
700 m.type);
701}
702
703static void print_outputs(const vector<OutputInfo>& outputs)
704{
705 for (unsigned i = 0; i < outputs.size(); ++i) {
706 const OutputInfo& o = outputs[i];
707
708 printf("Connector %u/@%u: %s", o.connector->idx(), o.connector->id(),
709 o.connector->fullname().c_str());
710
711 for (const PropInfo &prop: o.conn_props)
712 printf(" %s=%" PRIu64, prop.prop->name().c_str(),
713 prop.val);
714
715 printf("\n Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
716
717 for (const PropInfo &prop: o.crtc_props)
718 printf(" %s=%" PRIu64, prop.prop->name().c_str(),
719 prop.val);
720
721 printf(": %s\n", videomode_to_string(o.mode).c_str());
722
723 if (!o.legacy_fbs.empty()) {
724 auto fb = o.legacy_fbs[0];
725 printf(" (Fb %u %ux%u-%s)", fb->id(), fb->width(), fb->height(), PixelFormatToFourCC(fb->format()).c_str());
726 }
727
728 for (unsigned j = 0; j < o.planes.size(); ++j) {
729 const PlaneInfo& p = o.planes[j];
730 auto fb = p.fbs[0];
731 printf(" Plane %u/@%u: %u,%u-%ux%u", p.plane->idx(), p.plane->id(),
732 p.x, p.y, p.w, p.h);
733 for (const PropInfo &prop: p.props)
734 printf(" %s=%" PRIu64, prop.prop->name().c_str(),
735 prop.val);
736 printf("\n");
737
738 printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
739 PixelFormatToFourCC(fb->format()).c_str());
740 }
741 }
742}
743
744static void draw_test_patterns(const vector<OutputInfo>& outputs)
745{
746 for (const OutputInfo& o : outputs) {
747 for (auto fb : o.legacy_fbs)
748 draw_test_pattern(*fb);
749
750 for (const PlaneInfo& p : o.planes)
751 for (auto fb : p.fbs)
752 draw_test_pattern(*fb);
753 }
754}
755
756static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
757{
758 // Disable unused crtcs
759 for (Crtc* crtc : card.get_crtcs()) {
760 if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
761 continue;
762
763 crtc->disable_mode();
764 }
765
766 for (const OutputInfo& o : outputs) {
767 auto conn = o.connector;
768 auto crtc = o.crtc;
769
770 if (!o.conn_props.empty() || !o.crtc_props.empty())
771 printf("WARNING: properties not set without atomic modesetting");
772
773 if (!o.legacy_fbs.empty()) {
774 auto fb = o.legacy_fbs[0];
775 int r = crtc->set_mode(conn, *fb, o.mode);
776 if (r)
777 printf("crtc->set_mode() failed for crtc %u: %s\n",
778 crtc->id(), strerror(-r));
779 }
780
781 for (const PlaneInfo& p : o.planes) {
782 auto fb = p.fbs[0];
783 int r = crtc->set_plane(p.plane, *fb,
784 p.x, p.y, p.w, p.h,
785 0, 0, fb->width(), fb->height());
786 if (r)
787 printf("crtc->set_plane() failed for plane %u: %s\n",
788 p.plane->id(), strerror(-r));
789 if (!p.props.empty())
790 printf("WARNING: properties not set without atomic modesetting");
791 }
792 }
793}
794
795static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
796{
797 int r;
798
799 // XXX DRM framework doesn't allow moving an active plane from one crtc to another.
800 // See drm_atomic.c::plane_switching_crtc().
801 // For the time being, disable all crtcs and planes here.
802
803 AtomicReq disable_req(card);
804
805 // Disable unused crtcs
806 for (Crtc* crtc : card.get_crtcs()) {
807 //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
808 // continue;
809
810 disable_req.add(crtc, {
811 { "ACTIVE", 0 },
812 });
813 }
814
815 // Disable unused planes
816 for (Plane* plane : card.get_planes())
817 disable_req.add(plane, {
818 { "FB_ID", 0 },
819 { "CRTC_ID", 0 },
820 });
821
822 r = disable_req.commit_sync(true);
823 if (r)
824 EXIT("Atomic commit failed when disabling: %d\n", r);
825
826
827 // Keep blobs here so that we keep ref to them until we have committed the req
828 vector<unique_ptr<Blob>> blobs;
829
830 AtomicReq req(card);
831
832 for (const OutputInfo& o : outputs) {
833 auto conn = o.connector;
834 auto crtc = o.crtc;
835
836 blobs.emplace_back(o.mode.to_blob(card));
837 Blob* mode_blob = blobs.back().get();
838
839 req.add(conn, {
840 { "CRTC_ID", crtc->id() },
841 });
842
843 for (const PropInfo &prop: o.conn_props)
844 req.add(conn, prop.prop, prop.val);
845
846 req.add(crtc, {
847 { "ACTIVE", 1 },
848 { "MODE_ID", mode_blob->id() },
849 });
850
851 for (const PropInfo &prop: o.crtc_props)
852 req.add(crtc, prop.prop, prop.val);
853
854 for (const PlaneInfo& p : o.planes) {
855 auto fb = p.fbs[0];
856
857 req.add(p.plane, {
858 { "FB_ID", fb->id() },
859 { "CRTC_ID", crtc->id() },
860 { "SRC_X", (p.view_x ?: 0) << 16 },
861 { "SRC_Y", (p.view_y ?: 0) << 16 },
862 { "SRC_W", (p.view_w ?: fb->width()) << 16 },
863 { "SRC_H", (p.view_h ?: fb->height()) << 16 },
864 { "CRTC_X", p.x },
865 { "CRTC_Y", p.y },
866 { "CRTC_W", p.w },
867 { "CRTC_H", p.h },
868 });
869
870 for (const PropInfo &prop: p.props)
871 req.add(p.plane, prop.prop, prop.val);
872 }
873 }
874
875 r = req.test(true);
876 if (r)
877 EXIT("Atomic test failed: %d\n", r);
878
879 r = req.commit_sync(true);
880 if (r)
881 EXIT("Atomic commit failed: %d\n", r);
882}
883
884static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
885{
886 if (card.has_atomic())
887 set_crtcs_n_planes_atomic(card, outputs);
888 else
889 set_crtcs_n_planes_legacy(card, outputs);
890}
891
892static bool max_flips_reached;
893
894class FlipState : private PageFlipHandlerBase
895{
896public:
897 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
898 : m_card(card), m_name(name), m_outputs(outputs)
899 {
900 }
901
902 void start_flipping()
903 {
904 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
905 m_slowest_frame = std::chrono::duration<float>::min();
906 m_frame_num = 0;
907 queue_next();
908 }
909
910private:
911 void handle_page_flip(uint32_t frame, double time)
912 {
913 /*
914 * We get flip event for each crtc in this flipstate. We can commit the next frames
915 * only after we've gotten the flip event for all crtcs
916 */
917 if (++m_flip_count < m_outputs.size())
918 return;
919
920 m_frame_num++;
921 if (s_max_flips && m_frame_num >= s_max_flips)
922 max_flips_reached = true;
923
924 auto now = std::chrono::steady_clock::now();
925
926 std::chrono::duration<float> diff = now - m_prev_frame;
927 if (diff > m_slowest_frame)
928 m_slowest_frame = diff;
929
930 if (m_frame_num % 100 == 0) {
931 std::chrono::duration<float> fsec = now - m_prev_print;
932 printf("Connector %s: fps %f, slowest %.2f ms\n",
933 m_name.c_str(),
934 100.0 / fsec.count(),
935 m_slowest_frame.count() * 1000);
936 m_prev_print = now;
937 m_slowest_frame = std::chrono::duration<float>::min();
938 }
939
940 m_prev_frame = now;
941
942 queue_next();
943 }
944
945 static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num)
946 {
947 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
948 }
949
950 static void draw_bar(Framebuffer* fb, unsigned frame_num)
951 {
952 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
953 int new_xpos = get_bar_pos(fb, frame_num);
954
955 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
956 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
957 }
958
959 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
960 {
961 unsigned cur = frame_num % s_num_buffers;
962
963 for (const PlaneInfo& p : o.planes) {
964 auto fb = p.fbs[cur];
965
966 draw_bar(fb, frame_num);
967
968 req.add(p.plane, {
969 { "FB_ID", fb->id() },
970 });
971 }
972 }
973
974 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
975 {
976 unsigned cur = frame_num % s_num_buffers;
977
978 if (!o.legacy_fbs.empty()) {
979 auto fb = o.legacy_fbs[cur];
980
981 draw_bar(fb, frame_num);
982
983 int r = o.crtc->page_flip(*fb, this);
984 ASSERT(r == 0);
985 }
986
987 for (const PlaneInfo& p : o.planes) {
988 auto fb = p.fbs[cur];
989
990 draw_bar(fb, frame_num);
991
992 int r = o.crtc->set_plane(p.plane, *fb,
993 p.x, p.y, p.w, p.h,
994 0, 0, fb->width(), fb->height());
995 ASSERT(r == 0);
996 }
997 }
998
999 void queue_next()
1000 {
1001 m_flip_count = 0;
1002
1003 if (m_card.has_atomic()) {
1004 AtomicReq req(m_card);
1005
1006 for (auto o : m_outputs)
1007 do_flip_output(req, m_frame_num, *o);
1008
1009 int r = req.commit(this);
1010 if (r)
1011 EXIT("Flip commit failed: %d\n", r);
1012 } else {
1013 ASSERT(m_outputs.size() == 1);
1014 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
1015 }
1016 }
1017
1018 Card& m_card;
1019 string m_name;
1020 vector<const OutputInfo*> m_outputs;
1021 unsigned m_frame_num;
1022 unsigned m_flip_count;
1023
1024 chrono::steady_clock::time_point m_prev_print;
1025 chrono::steady_clock::time_point m_prev_frame;
1026 chrono::duration<float> m_slowest_frame;
1027
1028 static const unsigned bar_width = 20;
1029 static const unsigned bar_speed = 8;
1030};
1031
1032static void main_flip(Card& card, const vector<OutputInfo>& outputs)
1033{
1034 fd_set fds;
1035
1036 FD_ZERO(&fds);
1037
1038 int fd = card.fd();
1039
1040 vector<unique_ptr<FlipState>> flipstates;
1041
1042 if (!s_flip_sync) {
1043 for (const OutputInfo& o : outputs) {
1044 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
1045 flipstates.push_back(move(fs));
1046 }
1047 } else {
1048 vector<const OutputInfo*> ois;
1049
1050 string name;
1051 for (const OutputInfo& o : outputs) {
1052 name += to_string(o.connector->idx()) + ",";
1053 ois.push_back(&o);
1054 }
1055
1056 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1057 flipstates.push_back(move(fs));
1058 }
1059
1060 for (unique_ptr<FlipState>& fs : flipstates)
1061 fs->start_flipping();
1062
1063 while (!max_flips_reached) {
1064 int r;
1065
1066 FD_SET(0, &fds);
1067 FD_SET(fd, &fds);
1068
1069 r = select(fd + 1, &fds, NULL, NULL, NULL);
1070 if (r < 0) {
1071 fprintf(stderr, "select() failed with %d: %m\n", errno);
1072 break;
1073 } else if (FD_ISSET(0, &fds)) {
1074 fprintf(stderr, "Exit due to user-input\n");
1075 break;
1076 } else if (FD_ISSET(fd, &fds)) {
1077 card.call_page_flip_handlers();
1078 }
1079 }
1080}
1081
1082int main(int argc, char **argv)
1083{
1084 vector<Arg> output_args = parse_cmdline(argc, argv);
1085
1086 Card card(s_device_path);
1087
1088 if (!card.has_atomic() && s_flip_sync)
1089 EXIT("Synchronized flipping requires atomic modesetting");
1090
1091 ResourceManager resman(card);
1092
1093 vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1094
1095 if (!s_flip_mode)
1096 draw_test_patterns(outputs);
1097
1098 print_outputs(outputs);
1099
1100 set_crtcs_n_planes(card, outputs);
1101
1102 printf("press enter to exit\n");
1103
1104 if (s_flip_mode)
1105 main_flip(card, outputs);
1106 else
1107 getchar();
1108}