From a5c28bcb2ead34e921617711ebf94ffcb5d72878 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Mon, 23 May 2016 09:54:08 +0300 Subject: File/dir renames --- utils/CMakeLists.txt | 20 ++ utils/db.cpp | 265 ++++++++++++++++++++++ utils/fbtestpat.cpp | 60 +++++ utils/kmscapture.cpp | 435 +++++++++++++++++++++++++++++++++++++ utils/kmsprint.cpp | 221 +++++++++++++++++++ utils/kmsview.cpp | 92 ++++++++ utils/testpat.cpp | 604 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1697 insertions(+) create mode 100644 utils/CMakeLists.txt create mode 100644 utils/db.cpp create mode 100644 utils/fbtestpat.cpp create mode 100644 utils/kmscapture.cpp create mode 100644 utils/kmsprint.cpp create mode 100644 utils/kmsview.cpp create mode 100644 utils/testpat.cpp (limited to 'utils') 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 +#include +#include + +#include +#include +#include + +#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::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 diff = now - m_time_last; + if (diff > m_slowest_frame) + m_slowest_frame = diff; + + if (m_frame_num % 100 == 0) { + std::chrono::duration 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::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 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 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 m_fb; /* framebuffers for DRM buffers */ + vector 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 glob(const std::string& pat) +{ + glob_t glob_result; + glob(pat.c_str(), 0, NULL, &glob_result); + vector 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= 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 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 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 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 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 +#include +#include + +#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= Print object \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(ob)) + print_connector(*co, 0); + else if (auto en = dynamic_cast(ob)) + print_encoder(*en, 0); + else if (auto cr = dynamic_cast(ob)) + print_crtc(*cr, 0); + else if (auto pl = dynamic_cast(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 +#include +#include + +#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 \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 +#include +#include +#include +#include + +#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 planes; +}; + +static bool s_use_dmt; +static bool s_use_cea; + +static set s_used_crtcs; +static set 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 \n" + " -r, --crtc=CRTC CRTC is [:]x[@]\n" + " -p, --plane=PLANE PLANE is [:][,-]x\n" + " -f, --fb=FB FB is [x][-][<4cc>]\n" + " --dmt Search for the given mode from DMT tables\n" + " --cea Search for the given mode from CEA tables\n" + "\n" + ", and can be given by id () or index (@).\n" + " 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 parse_cmdline(int argc, char **argv) +{ + vector 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 setups_to_outputs(Card& card, const vector& output_args) +{ + vector 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 = ¤t_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& 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& 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 output_args = parse_cmdline(argc, argv); + + Card card(s_device_path); + + vector outputs = setups_to_outputs(card, output_args); + + print_outputs(outputs); + + set_crtcs_n_planes(card, outputs); + + printf("press enter to exit\n"); + + getchar(); +} -- cgit v1.2.3