diff options
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | utils/db.cpp | 265 | ||||
| -rw-r--r-- | utils/fbtestpat.cpp | 60 | ||||
| -rw-r--r-- | utils/kmscapture.cpp | 435 | ||||
| -rw-r--r-- | utils/kmsprint.cpp | 221 | ||||
| -rw-r--r-- | utils/kmsview.cpp | 92 | ||||
| -rw-r--r-- | utils/testpat.cpp | 604 | 
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 = ¤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<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(); +}  | 
