summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorTomi Valkeinen <tomi.valkeinen@ti.com>2016-05-23 09:54:08 +0300
committerTomi Valkeinen <tomi.valkeinen@ti.com>2016-05-23 09:54:08 +0300
commita5c28bcb2ead34e921617711ebf94ffcb5d72878 (patch)
treee2f93259f63407357c70b06a7d59c24fde5a3901 /utils
parent0bc5bbd6766949d651f98e12981d79c86ce0bf99 (diff)
File/dir renames
Diffstat (limited to 'utils')
-rw-r--r--utils/CMakeLists.txt20
-rw-r--r--utils/db.cpp265
-rw-r--r--utils/fbtestpat.cpp60
-rw-r--r--utils/kmscapture.cpp435
-rw-r--r--utils/kmsprint.cpp221
-rw-r--r--utils/kmsview.cpp92
-rw-r--r--utils/testpat.cpp604
7 files changed, 1697 insertions, 0 deletions
diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt
new file mode 100644
index 0000000..3a7115e
--- /dev/null
+++ b/utils/CMakeLists.txt
@@ -0,0 +1,20 @@
+include_directories(${LIBDRM_INCLUDE_DIRS})
+link_directories(${LIBDRM_LIBRARY_DIRS})
+
+add_executable (db db.cpp)
+target_link_libraries(db kms++ kms++util ${LIBDRM_LIBRARIES})
+
+add_executable (testpat testpat.cpp)
+target_link_libraries(testpat kms++ kms++util ${LIBDRM_LIBRARIES})
+
+add_executable (kmsview kmsview.cpp)
+target_link_libraries(kmsview kms++ kms++util ${LIBDRM_LIBRARIES})
+
+add_executable (kmsprint kmsprint.cpp)
+target_link_libraries(kmsprint kms++ kms++util ${LIBDRM_LIBRARIES})
+
+add_executable (fbtestpat fbtestpat.cpp)
+target_link_libraries(fbtestpat kms++util)
+
+add_executable (kmscapture kmscapture.cpp)
+target_link_libraries(kmscapture kms++ kms++util ${LIBDRM_LIBRARIES})
diff --git a/utils/db.cpp b/utils/db.cpp
new file mode 100644
index 0000000..3e8420b
--- /dev/null
+++ b/utils/db.cpp
@@ -0,0 +1,265 @@
+#include <cstdio>
+#include <algorithm>
+#include <chrono>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <drm_fourcc.h>
+
+#include "kms++.h"
+
+#include "test.h"
+
+using namespace std;
+using namespace kms;
+
+static void main_loop(Card& card);
+
+class Flipper
+{
+public:
+ Flipper(Card& card, unsigned width, unsigned height)
+ : m_current(0), m_bar_xpos(0)
+ {
+ auto format = PixelFormat::XRGB8888;
+ m_fbs[0] = new DumbFramebuffer(card, width, height, format);
+ m_fbs[1] = new DumbFramebuffer(card, width, height, format);
+ }
+
+ ~Flipper()
+ {
+ delete m_fbs[0];
+ delete m_fbs[1];
+ }
+
+ Framebuffer* get_next()
+ {
+ m_current ^= 1;
+
+ const int bar_width = 20;
+ const int bar_speed = 8;
+
+ auto fb = m_fbs[m_current];
+
+ int current_xpos = m_bar_xpos;
+ int old_xpos = (current_xpos + (fb->width() - bar_width - bar_speed)) % (fb->width() - bar_width);
+ int new_xpos = (current_xpos + bar_speed) % (fb->width() - bar_width);
+
+ draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
+
+ m_bar_xpos = new_xpos;
+
+ return fb;
+ }
+
+private:
+ DumbFramebuffer* m_fbs[2];
+
+ int m_current;
+ int m_bar_xpos;
+};
+
+class OutputFlipHandler : private PageFlipHandlerBase
+{
+public:
+ OutputFlipHandler(Connector* conn, Crtc* crtc, const Videomode& mode)
+ : m_connector(conn), m_crtc(crtc), m_mode(mode),
+ m_flipper(conn->card(), mode.hdisplay, mode.vdisplay),
+ m_plane(0), m_plane_flipper(0)
+ {
+ }
+
+ OutputFlipHandler(Connector* conn, Crtc* crtc, const Videomode& mode,
+ Plane* plane, unsigned pwidth, unsigned pheight)
+ : m_connector(conn), m_crtc(crtc), m_mode(mode),
+ m_flipper(conn->card(), mode.hdisplay, mode.vdisplay),
+ m_plane(plane)
+ {
+ m_plane_flipper = new Flipper(conn->card(), pwidth, pheight);
+ }
+
+ ~OutputFlipHandler()
+ {
+ if (m_plane_flipper)
+ delete m_plane_flipper;
+ }
+
+ OutputFlipHandler(const OutputFlipHandler& other) = delete;
+ OutputFlipHandler& operator=(const OutputFlipHandler& other) = delete;
+
+ void set_mode()
+ {
+ auto fb = m_flipper.get_next();
+ int r = m_crtc->set_mode(m_connector, *fb, m_mode);
+ ASSERT(r == 0);
+
+ if (m_crtc->card().has_atomic())
+ m_root_plane = m_crtc->get_primary_plane();
+
+ if (m_plane) {
+ auto planefb = m_plane_flipper->get_next();
+ r = m_crtc->set_plane(m_plane, *planefb,
+ 0, 0, planefb->width(), planefb->height(),
+ 0, 0, planefb->width(), planefb->height());
+ ASSERT(r == 0);
+ }
+ }
+
+ void start_flipping()
+ {
+ m_time_last = m_t1 = std::chrono::steady_clock::now();
+ m_slowest_frame = std::chrono::duration<float>::min();
+ m_frame_num = 0;
+ queue_next();
+ }
+
+private:
+ void handle_page_flip(uint32_t frame, double time)
+ {
+ ++m_frame_num;
+
+ auto now = std::chrono::steady_clock::now();
+
+ std::chrono::duration<float> diff = now - m_time_last;
+ if (diff > m_slowest_frame)
+ m_slowest_frame = diff;
+
+ if (m_frame_num % 100 == 0) {
+ std::chrono::duration<float> fsec = now - m_t1;
+ printf("Output %d: fps %f, slowest %.2f ms\n",
+ m_connector->idx(), 100.0 / fsec.count(),
+ m_slowest_frame.count() * 1000);
+ m_t1 = now;
+ m_slowest_frame = std::chrono::duration<float>::min();
+ }
+
+ m_time_last = now;
+
+ queue_next();
+ }
+
+ void queue_next()
+ {
+ auto crtc = m_crtc;
+ auto& card = crtc->card();
+
+ auto fb = m_flipper.get_next();
+ Framebuffer* planefb = m_plane ? m_plane_flipper->get_next() : 0;
+
+ if (card.has_atomic()) {
+ int r;
+
+ AtomicReq req(card);
+
+ req.add(m_root_plane, "FB_ID", fb->id());
+ if (m_plane)
+ req.add(m_plane, "FB_ID", planefb->id());
+
+ r = req.test();
+ ASSERT(r == 0);
+
+ r = req.commit(this);
+ ASSERT(r == 0);
+ } else {
+ int r = crtc->page_flip(*fb, this);
+ ASSERT(r == 0);
+
+ if (m_plane) {
+ r = m_crtc->set_plane(m_plane, *planefb,
+ 0, 0, planefb->width(), planefb->height(),
+ 0, 0, planefb->width(), planefb->height());
+ ASSERT(r == 0);
+ }
+ }
+ }
+
+private:
+ Connector* m_connector;
+ Crtc* m_crtc;
+ Videomode m_mode;
+ Plane* m_root_plane;
+
+ int m_frame_num;
+ chrono::steady_clock::time_point m_t1;
+ chrono::steady_clock::time_point m_time_last;
+ chrono::duration<float> m_slowest_frame;
+
+ Flipper m_flipper;
+
+ Plane* m_plane;
+ Flipper* m_plane_flipper;
+};
+
+int main()
+{
+ Card card;
+
+ if (card.master() == false)
+ printf("Not DRM master, modeset may fail\n");
+
+ vector<OutputFlipHandler*> outputs;
+
+ for (auto pipe : card.get_connected_pipelines())
+ {
+ auto conn = pipe.connector;
+ auto crtc = pipe.crtc;
+ auto mode = conn->get_default_mode();
+
+
+ Plane* plane = 0;
+#if 0 // disable the plane for now
+ for (Plane* p : crtc->get_possible_planes()) {
+ if (p->plane_type() == PlaneType::Overlay) {
+ plane = p;
+ break;
+ }
+ }
+#endif
+ OutputFlipHandler* output;
+ if (plane)
+ output = new OutputFlipHandler(conn, crtc, mode, plane, 500, 400);
+ else
+ output = new OutputFlipHandler(conn, crtc, mode);
+ outputs.push_back(output);
+ }
+
+ for(auto out : outputs)
+ out->set_mode();
+
+ for(auto out : outputs)
+ out->start_flipping();
+
+ main_loop(card);
+
+ for(auto out : outputs)
+ delete out;
+}
+
+static void main_loop(Card& card)
+{
+ fd_set fds;
+
+ FD_ZERO(&fds);
+
+ int fd = card.fd();
+
+ printf("press enter to exit\n");
+
+ while (true) {
+ int r;
+
+ FD_SET(0, &fds);
+ FD_SET(fd, &fds);
+
+ r = select(fd + 1, &fds, NULL, NULL, NULL);
+ if (r < 0) {
+ fprintf(stderr, "select() failed with %d: %m\n", errno);
+ break;
+ } else if (FD_ISSET(0, &fds)) {
+ fprintf(stderr, "exit due to user-input\n");
+ break;
+ } else if (FD_ISSET(fd, &fds)) {
+ card.call_page_flip_handlers();
+ }
+ }
+}
diff --git a/utils/fbtestpat.cpp b/utils/fbtestpat.cpp
new file mode 100644
index 0000000..d82f3e4
--- /dev/null
+++ b/utils/fbtestpat.cpp
@@ -0,0 +1,60 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <linux/fb.h>
+
+#include "test.h"
+#include "extcpuframebuffer.h"
+
+using namespace kms;
+
+int main(int argc, char** argv)
+{
+ const char* fbdev = "/dev/fb0";
+ int r;
+
+ int fd = open(fbdev, O_RDWR);
+ FAIL_IF(fd < 0, "open %s failed\n", fbdev);
+
+ struct fb_var_screeninfo var;
+
+ r = ioctl(fd, FBIOGET_VSCREENINFO, &var);
+ FAIL_IF(r, "FBIOGET_VSCREENINFO failed");
+
+ struct fb_fix_screeninfo fix;
+
+ r = ioctl(fd, FBIOGET_FSCREENINFO, &fix);
+ FAIL_IF(r, "FBIOGET_FSCREENINFO failed");
+
+ uint8_t* ptr = (uint8_t*)mmap(NULL,
+ var.yres_virtual * fix.line_length,
+ PROT_WRITE | PROT_READ,
+ MAP_SHARED, fd, 0);
+
+ FAIL_IF(ptr == MAP_FAILED, "mmap failed");
+
+ ExtCPUFramebuffer buf(var.xres, var.yres_virtual, PixelFormat::XRGB8888, ptr, fix.line_length);
+
+ printf("%s: res %dx%d, virtual %dx%d, line_len %d\n",
+ fbdev,
+ var.xres, var.yres,
+ var.xres_virtual, var.yres_virtual,
+ fix.line_length);
+
+ draw_test_pattern(buf);
+
+ for (unsigned y = 0; y < var.yres_virtual; ++y)
+ memcpy(ptr + fix.line_length * y, buf.map(0) + buf.stride(0) * y, buf.stride(0));
+
+ close(fd);
+
+ return 0;
+}
diff --git a/utils/kmscapture.cpp b/utils/kmscapture.cpp
new file mode 100644
index 0000000..ee700b7
--- /dev/null
+++ b/utils/kmscapture.cpp
@@ -0,0 +1,435 @@
+#include <linux/videodev2.h>
+#include <cstdio>
+#include <string.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <fstream>
+#include <sys/ioctl.h>
+#include <xf86drm.h>
+#include <glob.h>
+
+#include "kms++.h"
+#include "test.h"
+#include "opts.h"
+
+#define CAMERA_BUF_QUEUE_SIZE 3
+#define MAX_CAMERA 9
+
+using namespace std;
+using namespace kms;
+
+enum class BufferProvider {
+ DRM,
+ V4L2,
+};
+
+class CameraPipeline
+{
+public:
+ CameraPipeline(int cam_fd, Card& card, Crtc* crtc, Plane* plane, uint32_t x, uint32_t y,
+ uint32_t iw, uint32_t ih, PixelFormat pixfmt,
+ BufferProvider buffer_provider);
+ ~CameraPipeline();
+
+ CameraPipeline(const CameraPipeline& other) = delete;
+ CameraPipeline& operator=(const CameraPipeline& other) = delete;
+
+ void show_next_frame(AtomicReq &req);
+ int fd() const { return m_fd; }
+ void start_streaming();
+private:
+ ExtFramebuffer* GetExtFrameBuffer(Card& card, uint32_t i, PixelFormat pixfmt);
+ int m_fd; /* camera file descriptor */
+ Crtc* m_crtc;
+ Plane* m_plane;
+ BufferProvider m_buffer_provider;
+ vector<DumbFramebuffer*> m_fb; /* framebuffers for DRM buffers */
+ vector<ExtFramebuffer*> m_extfb; /* framebuffers for V4L2 buffers */
+ int m_prev_fb_index;
+ uint32_t m_in_width, m_in_height; /* camera capture resolution */
+ /* image properties for display */
+ uint32_t m_out_width, m_out_height;
+ uint32_t m_out_x, m_out_y;
+};
+
+static int buffer_export(int v4lfd, enum v4l2_buf_type bt, uint32_t index, int *dmafd)
+{
+ struct v4l2_exportbuffer expbuf;
+
+ memset(&expbuf, 0, sizeof(expbuf));
+ expbuf.type = bt;
+ expbuf.index = index;
+ if (ioctl(v4lfd, VIDIOC_EXPBUF, &expbuf) == -1) {
+ perror("VIDIOC_EXPBUF");
+ return -1;
+ }
+
+ *dmafd = expbuf.fd;
+
+ return 0;
+}
+
+ExtFramebuffer* CameraPipeline::GetExtFrameBuffer(Card& card, uint32_t i, PixelFormat pixfmt)
+{
+ int r, dmafd;
+
+ r = buffer_export(m_fd, V4L2_BUF_TYPE_VIDEO_CAPTURE, i, &dmafd);
+ ASSERT(r == 0);
+
+ uint32_t handle;
+ r = drmPrimeFDToHandle(card.fd(), dmafd, &handle);
+ ASSERT(r == 0);
+
+ const PixelFormatInfo& format_info = get_pixel_format_info(pixfmt);
+ ASSERT(format_info.num_planes == 1);
+
+ uint32_t handles[4] { handle };
+ uint32_t pitches[4] { m_in_width * (format_info.planes[0].bitspp / 8) };
+ uint32_t offsets[4] { };
+
+ return new ExtFramebuffer(card, m_in_width, m_in_height, pixfmt,
+ handles, pitches, offsets);
+}
+
+bool inline better_size(struct v4l2_frmsize_discrete* v4ldisc,
+ uint32_t iw, uint32_t ih,
+ uint32_t best_w, uint32_t best_h)
+{
+ if (v4ldisc->width <= iw && v4ldisc->height <= ih &&
+ (v4ldisc->width >= best_w || v4ldisc->height >= best_h))
+ return true;
+
+ return false;
+}
+
+CameraPipeline::CameraPipeline(int cam_fd, Card& card, Crtc *crtc, Plane* plane, uint32_t x, uint32_t y,
+ uint32_t iw, uint32_t ih, PixelFormat pixfmt,
+ BufferProvider buffer_provider)
+ : m_fd(cam_fd), m_crtc(crtc), m_buffer_provider(buffer_provider), m_prev_fb_index(-1)
+{
+
+ int r;
+ uint32_t best_w = 320;
+ uint32_t best_h = 240;
+
+ struct v4l2_frmsizeenum v4lfrms = { };
+ v4lfrms.pixel_format = (uint32_t)pixfmt;
+ while (ioctl(m_fd, VIDIOC_ENUM_FRAMESIZES, &v4lfrms) == 0) {
+ if (v4lfrms.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ if (better_size(&v4lfrms.discrete, iw, ih,
+ best_w, best_h)) {
+ best_w = v4lfrms.discrete.width;
+ best_h = v4lfrms.discrete.height;
+ }
+ } else {
+ break;
+ }
+ v4lfrms.index++;
+ };
+
+ m_out_width = m_in_width = best_w;
+ m_out_height = m_in_height = best_h;
+ /* Move it to the middle of the requested area */
+ m_out_x = x + iw / 2 - m_out_width / 2;
+ m_out_y = y + ih / 2 - m_out_height / 2;
+
+ struct v4l2_format v4lfmt = { };
+ v4lfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ r = ioctl(m_fd, VIDIOC_G_FMT, &v4lfmt);
+ ASSERT(r == 0);
+
+ v4lfmt.fmt.pix.pixelformat = (uint32_t)pixfmt;
+ v4lfmt.fmt.pix.width = m_in_width;
+ v4lfmt.fmt.pix.height = m_in_height;
+
+ r = ioctl(m_fd, VIDIOC_S_FMT, &v4lfmt);
+ ASSERT(r == 0);
+
+ uint32_t v4l_mem;
+
+ if (m_buffer_provider == BufferProvider::V4L2)
+ v4l_mem = V4L2_MEMORY_MMAP;
+ else
+ v4l_mem = V4L2_MEMORY_DMABUF;
+
+ struct v4l2_requestbuffers v4lreqbuf = { };
+ v4lreqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ v4lreqbuf.memory = v4l_mem;
+ v4lreqbuf.count = CAMERA_BUF_QUEUE_SIZE;
+ r = ioctl(m_fd, VIDIOC_REQBUFS, &v4lreqbuf);
+ ASSERT(r == 0);
+ ASSERT(v4lreqbuf.count == CAMERA_BUF_QUEUE_SIZE);
+
+ struct v4l2_buffer v4lbuf = { };
+ v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ v4lbuf.memory = v4l_mem;
+
+ for (unsigned i = 0; i < CAMERA_BUF_QUEUE_SIZE; i++) {
+ DumbFramebuffer *fb = NULL;
+ ExtFramebuffer *extfb = NULL;
+
+ if (m_buffer_provider == BufferProvider::V4L2)
+ extfb = GetExtFrameBuffer(card, i, pixfmt);
+ else
+ fb = new DumbFramebuffer(card, m_in_width,
+ m_in_height, pixfmt);
+
+ v4lbuf.index = i;
+ if (m_buffer_provider == BufferProvider::DRM)
+ v4lbuf.m.fd = fb->prime_fd(0);
+ r = ioctl(m_fd, VIDIOC_QBUF, &v4lbuf);
+ ASSERT(r == 0);
+
+ if (m_buffer_provider == BufferProvider::V4L2)
+ m_extfb.push_back(extfb);
+ else
+ m_fb.push_back(fb);
+ }
+
+ m_plane = plane;
+
+ // Do initial plane setup with first fb, so that we only need to
+ // set the FB when page flipping
+ AtomicReq req(card);
+
+ Framebuffer *fb;
+ if (m_buffer_provider == BufferProvider::V4L2)
+ fb = m_extfb[0];
+ else
+ fb = m_fb[0];
+
+ req.add(m_plane, "CRTC_ID", m_crtc->id());
+ req.add(m_plane, "FB_ID", fb->id());
+
+ req.add(m_plane, "CRTC_X", m_out_x);
+ req.add(m_plane, "CRTC_Y", m_out_y);
+ req.add(m_plane, "CRTC_W", m_out_width);
+ req.add(m_plane, "CRTC_H", m_out_height);
+
+ req.add(m_plane, "SRC_X", 0);
+ req.add(m_plane, "SRC_Y", 0);
+ req.add(m_plane, "SRC_W", m_in_width << 16);
+ req.add(m_plane, "SRC_H", m_in_height << 16);
+
+ r = req.commit_sync();
+ FAIL_IF(r, "initial plane setup failed");
+}
+
+CameraPipeline::~CameraPipeline()
+{
+ for (unsigned i = 0; i < m_fb.size(); i++)
+ delete m_fb[i];
+
+ for (unsigned i = 0; i < m_extfb.size(); i++)
+ delete m_extfb[i];
+
+ ::close(m_fd);
+}
+
+void CameraPipeline::start_streaming()
+{
+ enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ int r = ioctl(m_fd, VIDIOC_STREAMON, &type);
+ FAIL_IF(r, "Failed to enable camera stream: %d", r);
+}
+
+void CameraPipeline::show_next_frame(AtomicReq& req)
+{
+ int r;
+ uint32_t v4l_mem;
+
+ if (m_buffer_provider == BufferProvider::V4L2)
+ v4l_mem = V4L2_MEMORY_MMAP;
+ else
+ v4l_mem = V4L2_MEMORY_DMABUF;
+
+ struct v4l2_buffer v4l2buf = { };
+ v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ v4l2buf.memory = v4l_mem;
+ r = ioctl(m_fd, VIDIOC_DQBUF, &v4l2buf);
+ if (r != 0) {
+ printf("VIDIOC_DQBUF ioctl failed with %d\n", errno);
+ return;
+ }
+
+ unsigned fb_index = v4l2buf.index;
+
+ Framebuffer *fb;
+ if (m_buffer_provider == BufferProvider::V4L2)
+ fb = m_extfb[fb_index];
+ else
+ fb = m_fb[fb_index];
+
+ req.add(m_plane, "FB_ID", fb->id());
+
+ if (m_prev_fb_index >= 0) {
+ memset(&v4l2buf, 0, sizeof(v4l2buf));
+ v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ v4l2buf.memory = v4l_mem;
+ v4l2buf.index = m_prev_fb_index;
+ if (m_buffer_provider == BufferProvider::DRM)
+ v4l2buf.m.fd = m_fb[m_prev_fb_index]->prime_fd(0);
+ r = ioctl(m_fd, VIDIOC_QBUF, &v4l2buf);
+ ASSERT(r == 0);
+
+ }
+
+ m_prev_fb_index = fb_index;
+}
+
+static bool is_capture_dev(int fd)
+{
+ struct v4l2_capability cap = { };
+ int r = ioctl(fd, VIDIOC_QUERYCAP, &cap);
+ ASSERT(r == 0);
+ return cap.capabilities & V4L2_CAP_VIDEO_CAPTURE;
+}
+
+std::vector<std::string> glob(const std::string& pat)
+{
+ glob_t glob_result;
+ glob(pat.c_str(), 0, NULL, &glob_result);
+ vector<string> ret;
+ for(unsigned i = 0; i < glob_result.gl_pathc; ++i)
+ ret.push_back(string(glob_result.gl_pathv[i]));
+ globfree(&glob_result);
+ return ret;
+}
+
+static const char* usage_str =
+ "Usage: kmscapture [OPTIONS]\n\n"
+ "Options:\n"
+ " -s, --single Single camera mode. Open only /dev/video0\n"
+ " --buffer-type=<drm|v4l> Use DRM or V4L provided buffers. Default: DRM\n"
+ " -h, --help Print this help\n"
+ ;
+
+int main(int argc, char** argv)
+{
+ BufferProvider buffer_provider = BufferProvider::DRM;
+ bool single_cam = false;
+
+ OptionSet optionset = {
+ Option("s|single", [&]()
+ {
+ single_cam = true;
+ }),
+ Option("|buffer-type=", [&](string s)
+ {
+ if (s == "v4l")
+ buffer_provider = BufferProvider::V4L2;
+ else if (s == "drm")
+ buffer_provider = BufferProvider::DRM;
+ else
+ FAIL("Invalid buffer provider: %s", s.c_str());
+ }),
+ Option("h|help", [&]()
+ {
+ puts(usage_str);
+ exit(-1);
+ }),
+ };
+
+ optionset.parse(argc, argv);
+
+ if (optionset.params().size() > 0) {
+ puts(usage_str);
+ exit(-1);
+ }
+
+ auto pixfmt = PixelFormat::YUYV;
+
+ Card card;
+
+ auto conn = card.get_first_connected_connector();
+ auto crtc = conn->get_current_crtc();
+ printf("Display: %dx%d\n", crtc->width(), crtc->height());
+ printf("Buffer provider: %s\n", buffer_provider == BufferProvider::V4L2? "V4L" : "DRM");
+
+ vector<int> camera_fds;
+
+ for (string vidpath : glob("/dev/video*")) {
+ int fd = ::open(vidpath.c_str(), O_RDWR | O_NONBLOCK);
+
+ if (fd < 0)
+ continue;
+
+ if (!is_capture_dev(fd)) {
+ close(fd);
+ continue;
+ }
+
+ camera_fds.push_back(fd);
+
+ if (single_cam)
+ break;
+ }
+
+ FAIL_IF(camera_fds.size() == 0, "No cameras found");
+
+ vector<Plane*> available_planes;
+ for (Plane* p : crtc->get_possible_planes()) {
+ if (p->plane_type() != PlaneType::Overlay)
+ continue;
+
+ if (!p->supports_format(pixfmt))
+ continue;
+
+ available_planes.push_back(p);
+ }
+
+ FAIL_IF(available_planes.size() < camera_fds.size(), "Not enough video planes for cameras");
+
+ uint32_t plane_w = crtc->width() / camera_fds.size();
+ vector<CameraPipeline*> cameras;
+
+ for (unsigned i = 0; i < camera_fds.size(); ++i) {
+ int cam_fd = camera_fds[i];
+ Plane* plane = available_planes[i];
+
+ auto cam = new CameraPipeline(cam_fd, card, crtc, plane, i * plane_w, 0,
+ plane_w, crtc->height(), pixfmt, buffer_provider);
+ cameras.push_back(cam);
+ }
+
+ unsigned nr_cameras = cameras.size();
+
+ vector<pollfd> fds(nr_cameras + 1);
+
+ for (unsigned i = 0; i < nr_cameras; i++) {
+ fds[i].fd = cameras[i]->fd();
+ fds[i].events = POLLIN;
+ }
+ fds[nr_cameras].fd = 0;
+ fds[nr_cameras].events = POLLIN;
+
+ for (auto cam : cameras)
+ cam->start_streaming();
+
+ while (true) {
+ int r = poll(fds.data(), nr_cameras + 1, -1);
+ ASSERT(r > 0);
+
+ if (fds[nr_cameras].revents != 0)
+ break;
+
+ AtomicReq req(card);
+
+ for (unsigned i = 0; i < nr_cameras; i++) {
+ if (!fds[i].revents)
+ continue;
+ cameras[i]->show_next_frame(req);
+ fds[i].revents = 0;
+ }
+
+ r = req.test();
+ FAIL_IF(r, "Atomic commit failed: %d", r);
+
+ req.commit_sync();
+ }
+
+ for (auto cam : cameras)
+ delete cam;
+}
diff --git a/utils/kmsprint.cpp b/utils/kmsprint.cpp
new file mode 100644
index 0000000..7b9de8c
--- /dev/null
+++ b/utils/kmsprint.cpp
@@ -0,0 +1,221 @@
+#include <cstdio>
+#include <algorithm>
+#include <iostream>
+
+#include "kms++.h"
+#include "opts.h"
+
+using namespace std;
+using namespace kms;
+
+namespace kmsprint {
+
+static struct {
+ bool print_props;
+ bool print_modes;
+ bool recurse;
+} opts;
+
+string width(int w, string str)
+{
+ str.resize(w, ' ');
+ return str;
+}
+
+void print_mode(const Videomode &m, int ind)
+{
+ printf("%s%s %6d %4d %4d %4d %4d %d %4d %4d %4d %4d %d %2d 0x%04x %2d\n",
+ width(ind, "").c_str(),
+ m.name[0] == '\0' ? "" : width(11, m.name).c_str(),
+ m.clock,
+ m.hdisplay,
+ m.hsync_start,
+ m.hsync_end,
+ m.htotal,
+ m.hskew,
+ m.vdisplay,
+ m.vsync_start,
+ m.vsync_end,
+ m.vtotal,
+ m.vscan,
+ m.vrefresh,
+ m.flags,
+ m.type);
+}
+
+void print_property(uint64_t val, const Property& p, int ind)
+{
+ printf("%s%s (id %d) = %s\n", width(ind, "").c_str(),
+ p.name().c_str(), p.id(), p.to_str(val).c_str());
+}
+
+void print_properties(DrmObject& o, int ind)
+{
+ auto pmap = o.get_prop_map();
+ printf("%sProperties, %u in total:\n", width(ind, "").c_str(),
+ (unsigned) pmap.size());
+ for (auto pp : pmap) {
+ const Property& p = *o.card().get_prop(pp.first);
+ print_property(pp.second, p, ind + 2);
+ }
+}
+
+void print_plane(Plane& p, int ind)
+{
+ printf("%sPlane Id %d %d,%d -> %dx%d formats:", width(ind, "").c_str(),
+ p.id(), p.crtc_x(), p.crtc_y(), p.x(), p.y());
+ for (auto f : p.get_formats())
+ printf(" %s", PixelFormatToFourCC(f).c_str());
+ printf("\n");
+
+ if (opts.print_props)
+ print_properties(p, ind+2);
+}
+
+void print_crtc(Crtc& cc, int ind)
+{
+ printf("%sCRTC Id %d BufferId %d %dx%d at %dx%d gamma_size %d\n",
+ width(ind, "").c_str(), cc.id(), cc.buffer_id(), cc.width(),
+ cc.height(), cc.x(), cc.y(), cc.gamma_size());
+
+ printf("%s Mode ", width(ind, "").c_str());
+ print_mode(cc.mode(), 0);
+
+ if (opts.print_props)
+ print_properties(cc, ind+2);
+
+ if (opts.recurse)
+ for (auto p : cc.get_possible_planes())
+ print_plane(*p, ind + 2);
+}
+
+void print_encoder(Encoder& e, int ind)
+{
+ printf("%sEncoder Id %d type %s\n", width(ind, "").c_str(),
+ e.id(), e.get_encoder_type().c_str());
+
+ if (opts.print_props)
+ print_properties(e, ind+2);
+
+ if (opts.recurse)
+ for (auto cc : e.get_possible_crtcs())
+ print_crtc(*cc, ind + 2);
+}
+
+void print_connector(Connector& c, int ind)
+{
+ printf("%sConnector %s Id %d %sconnected", width(ind, "").c_str(),
+ c.fullname().c_str(), c.id(), c.connected() ? "" : "dis");
+ if (c.subpixel() != 0)
+ printf(" Subpixel: %s", c.subpixel_str().c_str());
+ printf("\n");
+
+ if (opts.print_props)
+ print_properties(c, ind+2);
+
+ if (opts.recurse)
+ for (auto enc : c.get_encoders())
+ print_encoder(*enc, ind + 2);
+
+ if (opts.print_modes) {
+ auto modes = c.get_modes();
+ printf("%sModes, %u in total:\n", width(ind + 2, "").c_str(),
+ (unsigned) modes.size());
+ for (auto mode : modes)
+ print_mode(mode, ind + 3);
+ }
+}
+
+}
+
+using namespace kmsprint;
+
+static const char* usage_str =
+ "Usage: kmsprint [OPTIONS]\n\n"
+ "Options:\n"
+ " -m, --modes Print modes\n"
+ " -p, --props Print properties\n"
+ " -r, --recurse Recursively print all related objects\n"
+ " --id=<ID> Print object <ID>\n"
+ ;
+
+static void usage()
+{
+ puts(usage_str);
+}
+
+int main(int argc, char **argv)
+{
+ string dev_path;
+ unsigned id = 0;
+
+ OptionSet optionset = {
+ Option("|device=",
+ [&](string s)
+ {
+ dev_path = s;
+ }),
+ Option("|id=",
+ [&](string s)
+ {
+ id = stoul(s);
+ }),
+ Option("p", [&](string s)
+ {
+ opts.print_props = true;
+ }),
+ Option("m", [&](string s)
+ {
+ opts.print_modes = true;
+ }),
+ Option("r", [&](string s)
+ {
+ opts.recurse = true;
+ }),
+ Option("h|help", [&]()
+ {
+ usage();
+ exit(-1);
+ }),
+ };
+
+ optionset.parse(argc, argv);
+
+ if (optionset.params().size() > 0) {
+ usage();
+ exit(-1);
+ }
+
+ Card card;
+
+ /* No options impliles recursion */
+ if (id == 0) {
+ opts.recurse = true;
+ for (auto conn : card.get_connectors())
+ print_connector(*conn, 0);
+ return 0;
+ } else {
+ auto ob = card.get_object(id);
+ if (!ob) {
+ cerr << "kmsprint" << ": Object id " <<
+ id << " not found." << endl;
+ return -1;
+ }
+
+ if (auto co = dynamic_cast<Connector*>(ob))
+ print_connector(*co, 0);
+ else if (auto en = dynamic_cast<Encoder*>(ob))
+ print_encoder(*en, 0);
+ else if (auto cr = dynamic_cast<Crtc*>(ob))
+ print_crtc(*cr, 0);
+ else if (auto pl = dynamic_cast<Plane*>(ob))
+ print_plane(*pl, 0);
+ else {
+ cerr << "kmsprint" << ": Unkown DRM Object type" <<
+ endl;
+ return -1;
+ }
+
+ return 0;
+ }
+}
diff --git a/utils/kmsview.cpp b/utils/kmsview.cpp
new file mode 100644
index 0000000..aae7e80
--- /dev/null
+++ b/utils/kmsview.cpp
@@ -0,0 +1,92 @@
+#include <cstdio>
+#include <fstream>
+#include <unistd.h>
+
+#include "kms++.h"
+
+#include "test.h"
+
+using namespace std;
+using namespace kms;
+
+static void read_frame(ifstream& is, DumbFramebuffer* fb, Crtc* crtc, Plane* plane)
+{
+ for (unsigned i = 0; i < fb->num_planes(); ++i)
+ is.read((char*)fb->map(i), fb->size(i));
+
+ unsigned w = min(crtc->width(), fb->width());
+ unsigned h = min(crtc->height(), fb->height());
+
+ int r = crtc->set_plane(plane, *fb,
+ 0, 0, w, h,
+ 0, 0, fb->width(), fb->height());
+
+ ASSERT(r == 0);
+}
+
+int main(int argc, char** argv)
+{
+ if (argc != 5) {
+ printf("Usage: %s <file> <width> <height> <fourcc>\n", argv[0]);
+ return -1;
+ }
+
+ string filename = argv[1];
+ uint32_t w = stoi(argv[2]);
+ uint32_t h = stoi(argv[3]);
+ string modestr = argv[4];
+
+ auto pixfmt = FourCCToPixelFormat(modestr);
+
+
+ ifstream is(filename, ifstream::binary);
+
+ is.seekg(0, std::ios::end);
+ unsigned fsize = is.tellg();
+ is.seekg(0);
+
+
+ Card card;
+
+ auto conn = card.get_first_connected_connector();
+ auto crtc = conn->get_current_crtc();
+
+ auto fb = new DumbFramebuffer(card, w, h, pixfmt);
+
+ Plane* plane = 0;
+
+ for (Plane* p : crtc->get_possible_planes()) {
+ if (p->plane_type() != PlaneType::Overlay)
+ continue;
+
+ if (!p->supports_format(pixfmt))
+ continue;
+
+ plane = p;
+ break;
+ }
+
+ FAIL_IF(!plane, "available plane not found");
+
+
+ unsigned frame_size = 0;
+ for (unsigned i = 0; i < fb->num_planes(); ++i)
+ frame_size += fb->size(i);
+
+ unsigned num_frames = fsize / frame_size;
+ printf("file size %u, frame size %u, frames %u\n", fsize, frame_size, num_frames);
+
+ for (unsigned i = 0; i < num_frames; ++i) {
+ printf("frame %d\n", i);
+ read_frame(is, fb, crtc, plane);
+ usleep(1000*50);
+ }
+
+ printf("press enter to exit\n");
+
+ is.close();
+
+ getchar();
+
+ delete fb;
+}
diff --git a/utils/testpat.cpp b/utils/testpat.cpp
new file mode 100644
index 0000000..ede176c
--- /dev/null
+++ b/utils/testpat.cpp
@@ -0,0 +1,604 @@
+#include <cstdio>
+#include <cstring>
+#include <algorithm>
+#include <regex>
+#include <set>
+
+#include "kms++.h"
+#include "modedb.h"
+
+#include "test.h"
+#include "opts.h"
+
+using namespace std;
+using namespace kms;
+
+struct PlaneInfo
+{
+ Plane* plane;
+
+ unsigned x;
+ unsigned y;
+ unsigned w;
+ unsigned h;
+
+ DumbFramebuffer* fb;
+};
+
+struct OutputInfo
+{
+ Connector* connector;
+
+ Crtc* crtc;
+ Videomode mode;
+ bool user_set_crtc;
+ DumbFramebuffer* fb;
+
+ vector<PlaneInfo> planes;
+};
+
+static bool s_use_dmt;
+static bool s_use_cea;
+
+static set<Crtc*> s_used_crtcs;
+static set<Plane*> s_used_planes;
+
+__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());
+ }
+}
+
+static void get_default_connector(Card& card, OutputInfo& output)
+{
+ output.connector = card.get_first_connected_connector();
+ output.mode = output.connector->get_default_mode();
+}
+
+static void parse_connector(Card& card, const string& str, OutputInfo& output)
+{
+ Connector* conn = nullptr;
+
+ auto connectors = card.get_connectors();
+
+ 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);
+
+ 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);
+
+ conn = c;
+ }
+ }
+
+ 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());
+
+ 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+)(i)?)(?:@(\\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]);
+ bool ilace = sm[5].matched ? true : false;
+ unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
+
+ bool found_mode = false;
+
+ try {
+ output.mode = output.connector->get_mode(w, h, refresh, ilace);
+ found_mode = true;
+ } catch (exception& e) { }
+
+ if (!found_mode && s_use_dmt) {
+ try {
+ output.mode = find_dmt(w, h, refresh, ilace);
+ found_mode = true;
+ printf("Found mode from DMT\n");
+ } catch (exception& e) { }
+ }
+
+ if (!found_mode && s_use_cea) {
+ try {
+ output.mode = find_cea(w, h, refresh, ilace);
+ found_mode = true;
+ printf("Found mode from CEA\n");
+ } catch (exception& e) { }
+ }
+
+ if (!found_mode)
+ throw invalid_argument("Mode not found");
+}
+
+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+)");
+
+ 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"
+ " --dmt Search for the given mode from DMT tables\n"
+ " --cea Search for the given mode from CEA tables\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("|dmt", []()
+ {
+ s_use_dmt = true;
+ }),
+ Option("|cea", []()
+ {
+ s_use_cea = true;
+ }),
+ 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 = mode.hsync_start - mode.hdisplay;
+ unsigned hsw = mode.hsync_end - mode.hsync_start;
+ unsigned hbp = mode.htotal - mode.hsync_end;
+
+ unsigned vfp = mode.vsync_start - mode.vdisplay;
+ unsigned vsw = mode.vsync_end - mode.vsync_start;
+ unsigned vbp = mode.vtotal - mode.vsync_end;
+
+ float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
+ if (mode.flags & (1<<4)) // XXX interlace
+ hz *= 2;
+
+ char buf[256];
+
+ sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
+ mode.clock / 1000.0,
+ mode.hdisplay, hfp, hsw, hbp,
+ mode.vdisplay, vfp, vsw, vbp,
+ mode.vrefresh, hz);
+
+ 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());
+ }
+ }
+}
+
+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;
+
+ 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));
+ }
+
+ 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();
+}