testpat: big rewrite
authorTomi Valkeinen <tomi.valkeinen@ti.com>
Wed, 18 Nov 2015 20:15:43 +0000 (22:15 +0200)
committerTomi Valkeinen <tomi.valkeinen@ti.com>
Thu, 26 Nov 2015 20:45:18 +0000 (22:45 +0200)
tests/testpat.cpp

index 85c67a3938bc76e77036e75be2ae37630abd547a..c51d3ec4cc989ac373f610ff3b937e7c339da0b5 100644 (file)
 #include <cstdio>
 #include <algorithm>
+#include <regex>
+#include <set>
 
 #include "kms++.h"
 
 #include "test.h"
-#include "cmdoptions.h"
+#include "opts.h"
 
 using namespace std;
 using namespace kms;
 
-static map<string, CmdOption> options = {
-       { "m", HAS_PARAM("Set display mode, for example 1920x1080") },
+struct PlaneInfo
+{
+       Plane* plane;
+
+       unsigned x;
+       unsigned y;
+       unsigned w;
+       unsigned h;
+
+       DumbFramebuffer* fb;
 };
 
-int main(int argc, char **argv)
+struct OutputInfo
 {
-       Card card;
-       CmdOptions opts(argc, argv, options);
+       Connector* connector;
 
-       if (card.master() == false)
-               printf("Not DRM master, modeset may fail\n");
+       Crtc* crtc;
+       Videomode mode;
+       bool user_set_crtc;
+       DumbFramebuffer* fb;
 
-       auto pipes = card.get_connected_pipelines();
+       vector<PlaneInfo> planes;
+};
 
-       vector<Framebuffer*> fbs;
+static set<Crtc*> s_used_crtcs;
+static set<Plane*> s_used_planes;
 
-       for (auto pipe : pipes)
-       {
-               auto conn = pipe.connector;
-               auto crtc = pipe.crtc;
+__attribute__ ((unused))
+static void print_regex_match(smatch sm)
+{
+       for (unsigned i = 0; i < sm.size(); ++i) {
+               string str = sm[i].str();
+               printf("%u: %s\n", i, str.c_str());
+       }
+}
 
-               // RG16 XR24 UYVY YUYV NV12
+static void get_default_connector(Card& card, OutputInfo& output)
+{
+       output.connector = card.get_first_connected_connector();
+       output.mode = output.connector->get_default_mode();
+}
 
-               auto mode = conn->get_default_mode();
+static void parse_connector(Card& card, const string& str, OutputInfo& output)
+{
+       Connector* conn = nullptr;
 
-               if (opts.is_set("m"))
-                       mode = conn->get_mode(opts.opt_param("m"));
+       auto connectors = card.get_connectors();
 
-               auto fb = new DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, PixelFormat::XRGB8888);
-               draw_test_pattern(*fb);
-               fbs.push_back(fb);
+       if (str[0] == '@') {
+               char* endptr;
+               unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
+               if (*endptr == 0) {
+                       if (idx >= connectors.size())
+                               EXIT("Bad connector number '%u'", idx);
 
-               printf("conn %u, crtc %u, fb %u\n", conn->id(), crtc->id(), fb->id());
+                       conn = connectors[idx];
+               }
+       } else {
+               char* endptr;
+               unsigned id = strtoul(str.c_str(), &endptr, 10);
+               if (*endptr == 0) {
+                       Connector* c = card.get_connector(id);
+                       if (!c)
+                               EXIT("Bad connector id '%u'", id);
 
-               int r = crtc->set_mode(conn, *fb, mode);
-               ASSERT(r == 0);
+                       conn = c;
+               }
        }
 
-       for (auto pipe: pipes)
-       {
-               auto crtc = pipe.crtc;
+       if (!conn) {
+               auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
+               if (iter != connectors.end())
+                       conn = *iter;
+       }
+
+       if (!conn)
+               EXIT("No connector '%s'", str.c_str());
+
+       if (!conn->connected())
+               EXIT("Connector '%s' not connected", conn->fullname().c_str());
 
-               Plane* plane = 0;
+       output.connector = conn;
+       output.mode = output.connector->get_default_mode();
+}
+
+static void get_default_crtc(Card& card, OutputInfo& output)
+{
+       Crtc* crtc = output.connector->get_current_crtc();
+
+       if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
+               s_used_crtcs.insert(crtc);
+               output.crtc = crtc;
+               return;
+       }
+
+       for (const auto& possible : output.connector->get_possible_crtcs()) {
+               if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
+                       s_used_crtcs.insert(possible);
+                       output.crtc = possible;
+                       return;
+               }
+       }
+
+       EXIT("Could not find available crtc");
+}
+
+static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
+{
+       // @12:1920x1200-60
+       const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+))(?:-(\\d+))?");
+
+       smatch sm;
+       if (!regex_match(crtc_str, sm, mode_re))
+               EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
+
+       if (sm[2].matched) {
+               bool use_idx = sm[1].length() == 1;
+               unsigned num = stoul(sm[2].str());
+
+               if (use_idx) {
+                       auto crtcs = card.get_crtcs();
+
+                       if (num >= crtcs.size())
+                               EXIT("Bad crtc number '%u'", num);
+
+                       output.crtc = crtcs[num];
+               } else {
+                       Crtc* c = card.get_crtc(num);
+                       if (!c)
+                               EXIT("Bad crtc id '%u'", num);
+
+                       output.crtc = c;
+               }
+       } else {
+               output.crtc = output.connector->get_current_crtc();
+       }
+
+       unsigned w = stoul(sm[3]);
+       unsigned h = stoul(sm[4]);
+       unsigned refresh = 0;
+       if (sm[5].matched)
+               refresh = stoul(sm[5]);
+
+       output.mode = output.connector->get_mode(w, h, refresh);
+}
+
+static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
+{
+       // 3:400,400-400x400
+       const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
 
-               for (Plane* p : crtc->get_possible_planes()) {
-                       if (p->plane_type() == PlaneType::Overlay) {
-                               plane = p;
-                               break;
+       smatch sm;
+       if (!regex_match(plane_str, sm, plane_re))
+               EXIT("Failed to parse plane option '%s'", plane_str.c_str());
+
+       if (sm[2].matched) {
+               bool use_idx = sm[1].length() == 1;
+               unsigned num = stoul(sm[2].str());
+
+               if (use_idx) {
+                       auto planes = card.get_planes();
+
+                       if (num >= planes.size())
+                               EXIT("Bad plane number '%u'", num);
+
+                       pinfo.plane = planes[num];
+               } else {
+                       Plane* p = card.get_plane(num);
+                       if (!p)
+                               EXIT("Bad plane id '%u'", num);
+
+                       pinfo.plane = p;
+               }
+       } else {
+               for (Plane* p : output.crtc->get_possible_planes()) {
+                       if (s_used_planes.find(p) != s_used_planes.end())
+                               continue;
+
+                       if (p->plane_type() != PlaneType::Overlay)
+                               continue;
+
+                       pinfo.plane = p;
+               }
+
+               if (!pinfo.plane)
+                       EXIT("Failed to find available plane");
+       }
+
+       s_used_planes.insert(pinfo.plane);
+
+       pinfo.w = stoul(sm[5]);
+       pinfo.h = stoul(sm[6]);
+
+       if (sm[3].matched)
+               pinfo.x = stoul(sm[3]);
+       else
+               pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
+
+       if (sm[4].matched)
+               pinfo.y = stoul(sm[4]);
+       else
+               pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
+}
+
+static DumbFramebuffer* get_default_fb(Card& card, unsigned width, unsigned height)
+{
+       auto fb = new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888);
+       draw_test_pattern(*fb);
+       return fb;
+}
+
+static DumbFramebuffer* parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
+{
+       unsigned w = def_w;
+       unsigned h = def_h;
+       PixelFormat format = PixelFormat::XRGB8888;
+
+       if (!fb_str.empty()) {
+               // XXX the regexp is not quite correct
+               // 400x400-NV12
+               const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
+
+               smatch sm;
+               if (!regex_match(fb_str, sm, fb_re))
+                       EXIT("Failed to parse fb option '%s'", fb_str.c_str());
+
+               if (sm[1].matched)
+                       w = stoul(sm[1]);
+               if (sm[2].matched)
+                       h = stoul(sm[2]);
+               if (sm[3].matched)
+                       format = FourCCToPixelFormat(sm[3]);
+       }
+
+       auto fb = new DumbFramebuffer(card, w, h, format);
+       draw_test_pattern(*fb);
+       return fb;
+}
+
+static const char* usage_str =
+               "Usage: testpat [OPTION]...\n\n"
+               "Show a test pattern on a display or plane\n\n"
+               "Options:\n"
+               "      --device=DEVICE       DEVICE is the path to DRM card to open\n"
+               "  -c, --connector=CONN      CONN is <connector>\n"
+               "  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
+               "  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
+               "  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
+               "\n"
+               "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
+               "<connector> can also be given by name.\n"
+               "\n"
+               "Options can be given multiple times to set up multiple displays or planes.\n"
+               "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
+               "an earlier option.\n"
+               "If you omit parameters, testpat tries to guess what you mean\n"
+               "\n"
+               "Examples:\n"
+               "\n"
+               "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
+               "    testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
+               "XR24 framebuffer on first connected connector in the default mode:\n"
+               "    testpat -f XR24\n\n"
+               "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
+               "    testpat -p 400x400 -f XR24\n\n"
+               "Test pattern on the second connector with default mode:\n"
+               "    testpat -c @1\n"
+               ;
+
+static void usage()
+{
+       puts(usage_str);
+}
+
+enum class ObjectType
+{
+       Connector,
+       Crtc,
+       Plane,
+       Framebuffer,
+};
+
+struct Arg
+{
+       ObjectType type;
+       string arg;
+};
+
+static string s_device_path = "/dev/dri/card0";
+
+static vector<Arg> parse_cmdline(int argc, char **argv)
+{
+       vector<Arg> args;
+
+       OptionSet optionset = {
+               Option("|device=",
+               [&](string s)
+               {
+                       s_device_path = s;
+               }),
+               Option("c|connector=",
+               [&](string s)
+               {
+                       args.push_back(Arg { ObjectType::Connector, s });
+               }),
+               Option("r|crtc=", [&](string s)
+               {
+                       args.push_back(Arg { ObjectType::Crtc, s });
+               }),
+               Option("p|plane=", [&](string s)
+               {
+                       args.push_back(Arg { ObjectType::Plane, s });
+               }),
+               Option("f|fb=", [&](string s)
+               {
+                       args.push_back(Arg { ObjectType::Framebuffer, s });
+               }),
+               Option("h|help", [&]()
+               {
+                       usage();
+                       exit(-1);
+               }),
+       };
+
+       optionset.parse(argc, argv);
+
+       if (optionset.params().size() > 0) {
+               usage();
+               exit(-1);
+       }
+
+       return args;
+}
+
+static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
+{
+       vector<OutputInfo> outputs;
+
+       if (output_args.size() == 0) {
+               // no output args, show a pattern on all screens
+               for (auto& pipe : card.get_connected_pipelines()) {
+                       OutputInfo output = { };
+                       output.connector = pipe.connector;
+                       output.crtc = pipe.crtc;
+                       output.mode = output.connector->get_default_mode();
+
+                       output.fb = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
+
+                       outputs.push_back(output);
+               }
+
+               return outputs;
+       }
+
+       OutputInfo* current_output = 0;
+       PlaneInfo* current_plane = 0;
+
+       for (auto& arg : output_args) {
+               switch (arg.type) {
+               case ObjectType::Connector:
+               {
+                       outputs.push_back(OutputInfo { });
+                       current_output = &outputs.back();
+
+                       parse_connector(card, arg.arg, *current_output);
+                       current_plane = 0;
+
+                       break;
+               }
+
+               case ObjectType::Crtc:
+               {
+                       if (!current_output) {
+                               outputs.push_back(OutputInfo { });
+                               current_output = &outputs.back();
+                       }
+
+                       if (!current_output->connector)
+                               get_default_connector(card, *current_output);
+
+                       parse_crtc(card, arg.arg, *current_output);
+
+                       current_output->user_set_crtc = true;
+
+                       current_plane = 0;
+
+                       break;
+               }
+
+               case ObjectType::Plane:
+               {
+                       if (!current_output) {
+                               outputs.push_back(OutputInfo { });
+                               current_output = &outputs.back();
+                       }
+
+                       if (!current_output->connector)
+                               get_default_connector(card, *current_output);
+
+                       if (!current_output->crtc)
+                               get_default_crtc(card, *current_output);
+
+                       current_output->planes.push_back(PlaneInfo { });
+                       current_plane = &current_output->planes.back();
+
+                       parse_plane(card, arg.arg, *current_output, *current_plane);
+
+                       break;
+               }
+
+               case ObjectType::Framebuffer:
+               {
+                       if (!current_output) {
+                               outputs.push_back(OutputInfo { });
+                               current_output = &outputs.back();
+                       }
+
+                       if (!current_output->connector)
+                               get_default_connector(card, *current_output);
+
+                       if (!current_output->crtc)
+                               get_default_crtc(card, *current_output);
+
+                       int def_w, def_h;
+
+                       if (current_plane) {
+                               def_w = current_plane->w;
+                               def_h = current_plane->h;
+                       } else {
+                               def_w = current_output->mode.hdisplay;
+                               def_h = current_output->mode.vdisplay;
                        }
+
+                       auto fb = parse_fb(card, arg.arg, def_w, def_h);
+
+                       if (current_plane)
+                               current_plane->fb = fb;
+                       else
+                               current_output->fb = fb;
+
+                       break;
+               }
+               }
+       }
+
+       // create default framebuffers if needed
+       for (OutputInfo& o : outputs) {
+               if (!o.crtc) {
+                       get_default_crtc(card, *current_output);
+                       o.user_set_crtc = true;
+               }
+
+               if (!o.fb && o.user_set_crtc)
+                       o.fb = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
+
+               for (PlaneInfo &p : o.planes) {
+                       if (!p.fb)
+                               p.fb = get_default_fb(card, p.w, p.h);
+               }
+       }
+
+       return outputs;
+}
+
+static std::string videomode_to_string(const Videomode& mode)
+{
+       unsigned hfp, hsw, hbp;
+       unsigned vfp, vsw, vbp;
+
+       hfp = mode.hsync_start - mode.hdisplay;
+       hsw = mode.hsync_end - mode.hsync_start;
+       hbp = mode.htotal - mode.hsync_end;
+
+       vfp = mode.vsync_start - mode.vdisplay;
+       vsw = mode.vsync_end - mode.vsync_start;
+       vbp = mode.vtotal - mode.vsync_end;
+
+       char buf[256];
+
+       sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz",
+               mode.clock / 1000.0,
+               mode.hdisplay, hfp, hsw, hbp,
+               mode.vdisplay, vfp, vsw, vbp,
+               mode.vrefresh);
+
+       return std::string(buf);
+}
+
+static void print_outputs(const vector<OutputInfo>& outputs)
+{
+       for (unsigned i = 0; i < outputs.size(); ++i) {
+               const OutputInfo& o = outputs[i];
+
+               printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
+                      o.connector->fullname().c_str());
+               printf("  Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
+                      o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
+                      videomode_to_string(o.mode).c_str());
+               if (o.fb)
+                       printf("    Fb %ux%u-%s\n", o.fb->width(), o.fb->height(),
+                              PixelFormatToFourCC(o.fb->format()).c_str());
+
+               for (unsigned j = 0; j < o.planes.size(); ++j) {
+                       const PlaneInfo& p = o.planes[j];
+                       printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
+                              p.x, p.y, p.w, p.h);
+                       printf("    Fb %ux%u-%s\n", p.fb->width(), p.fb->height(),
+                              PixelFormatToFourCC(p.fb->format()).c_str());
                }
+       }
+}
 
-               if (plane) {
-                       auto planefb = new DumbFramebuffer(card, 400, 400, PixelFormat::YUYV);
-                       draw_test_pattern(*planefb);
-                       fbs.push_back(planefb);
+static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
+{
+       for (const OutputInfo& o : outputs) {
+               auto conn = o.connector;
+               auto crtc = o.crtc;
 
-                       int r = crtc->set_plane(plane, *planefb,
-                                           0, 0, planefb->width(), planefb->height(),
-                                           0, 0, planefb->width(), planefb->height());
+               if (o.fb) {
+                       int r = crtc->set_mode(conn, *o.fb, o.mode);
+                       if (r)
+                               printf("crtc->set_mode() failed for crtc %u: %s\n",
+                                      crtc->id(), strerror(-r));
+               }
 
-                       ASSERT(r == 0);
+               for (const PlaneInfo& p : o.planes) {
+                       int r = crtc->set_plane(p.plane, *p.fb,
+                                               p.x, p.y, p.w, p.h,
+                                               0, 0, p.fb->width(), p.fb->height());
+                       if (r)
+                               printf("crtc->set_plane() failed for plane %u: %s\n",
+                                      p.plane->id(), strerror(-r));
                }
        }
+}
+
+int main(int argc, char **argv)
+{
+       vector<Arg> output_args = parse_cmdline(argc, argv);
+
+       Card card(s_device_path);
+
+       vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
+
+       print_outputs(outputs);
+
+       set_crtcs_n_planes(card, outputs);
 
        printf("press enter to exit\n");
 
        getchar();
-
-       for(auto fb : fbs)
-               delete fb;
 }