diff options
Diffstat (limited to 'utils/kmstest.cpp')
-rw-r--r-- | utils/kmstest.cpp | 1108 |
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 | |||
18 | using namespace std; | ||
19 | using namespace kms; | ||
20 | |||
21 | struct 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 | |||
29 | struct 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 | |||
48 | struct 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 | |||
62 | static bool s_use_dmt; | ||
63 | static bool s_use_cea; | ||
64 | static unsigned s_num_buffers = 1; | ||
65 | static bool s_flip_mode; | ||
66 | static bool s_flip_sync; | ||
67 | static bool s_cvt; | ||
68 | static bool s_cvt_v2; | ||
69 | static bool s_cvt_vid_opt; | ||
70 | static unsigned s_max_flips; | ||
71 | |||
72 | __attribute__ ((unused)) | ||
73 | static 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 | |||
81 | static 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 | |||
95 | static 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 | |||
104 | static 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 | |||
113 | static 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 | |||
241 | static 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 | |||
290 | static 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 | |||
305 | static 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 | |||
311 | static 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 | |||
321 | static 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 | |||
364 | static 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 | |||
378 | static 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 | |||
421 | static void usage() | ||
422 | { | ||
423 | puts(usage_str); | ||
424 | } | ||
425 | |||
426 | enum class ArgType | ||
427 | { | ||
428 | Connector, | ||
429 | Crtc, | ||
430 | Plane, | ||
431 | Framebuffer, | ||
432 | View, | ||
433 | Property, | ||
434 | }; | ||
435 | |||
436 | struct Arg | ||
437 | { | ||
438 | ArgType type; | ||
439 | string arg; | ||
440 | }; | ||
441 | |||
442 | static string s_device_path = "/dev/dri/card0"; | ||
443 | |||
444 | static 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 | |||
528 | static 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 | |||
689 | static 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 | |||
703 | static 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 | |||
744 | static 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 | |||
756 | static 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 | |||
795 | static 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 | |||
884 | static 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 | |||
892 | static bool max_flips_reached; | ||
893 | |||
894 | class FlipState : private PageFlipHandlerBase | ||
895 | { | ||
896 | public: | ||
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 | |||
910 | private: | ||
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 | |||
1032 | static 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 | |||
1082 | int 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 | } | ||