最简单的DRM应用程序 (page-flip)

最简单的DRM应用程序 (page-flip)

参考代码:https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset-vsync.c

#include <utils/Log.h>
#include <unistd.h>
#include <inttypes.h>
#include <cutils/properties.h>

extern "C"{
#include<utils/Log.h>
}

#include <cutils/memory.h>
#include <unistd.h>
#include <utils/Log.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>

#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <drm.h>
#include <linux/ion.h>
#include <linux/msm_ion.h>
#include <libdrm_macros.h>
#include <thread>
#include <fstream>
#include <mutex>
#include <vector>

struct drmdisplay_buf;
struct drmdisplay_dev;
static int drmdisplay_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
			     struct drmdisplay_dev *dev);
static int drmdisplay_create_fb(int fd, struct drmdisplay_buf *buf);
static void drmdisplay_destory_fb(int fd, struct drmdisplay_buf *buf);
static int drmdisplay_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
			     struct drmdisplay_dev *dev);
static int drmdisplay_open(int *out, const char *node);
static int drmdisplay_prepare(int fd);
static void drmdisplay_draw(int fd);
static void drmdisplay_draw_dev(int fd, struct drmdisplay_dev *dev);
static void drmdisplay_cleanup(int fd);


struct drmdisplay_buf
{
    uint32_t width;
	uint32_t height;
	uint32_t stride;
	size_t size;
	uint32_t handle;
	uint8_t *map;
	uint32_t fb;
};


struct drmdisplay_dev {
    struct drmdisplay_dev *next;

	unsigned int front_buf;
	struct drmdisplay_buf bufs[2];

	drmModeModeInfo mode;
	uint32_t conn;
	int crtc;
	drmModeCrtc *saved_crtc;

	bool pflip_pending;
	bool cleanup;

	uint8_t r, g, b;
	bool r_up, g_up, b_up;
};

static struct drmdisplay_dev *drmdisplay_list = NULL;

static int drmdisplay_open(int *out, const char *node)
{
    int fd, ret;
    uint64_t has_dumb;

    fd = open(node, O_RDWR | O_CLOEXEC);
    if (fd < 0)
    {
        ret = -errno;
        fprintf(stderr, "open file fail: %d",ret);
        return ret;
    }

    if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb)
    {
        fprintf(stderr, "drm device '%s' does not support dumb buffers \n",node);
        close(fd);
        return -EOPNOTSUPP;
    }

    *out = fd;
    return 0;
}

static int drmdisplay_prepare(int fd)
{
	drmModeRes *res;
	drmModeConnector *conn;
	int i;
	struct drmdisplay_dev *dev;
	int ret;

	/* retrieve resources */
	res = drmModeGetResources(fd);
	if (!res) {
		fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", errno);
		return -errno;
	}

	/* iterate all connectors */
	fprintf(stderr, "drmdisplay_prepare res->count_connectors (%d): %m\n", res->count_connectors);
	for (i = 0; i < res->count_connectors; ++i) {
		/* get information for each connector */
		conn = drmModeGetConnector(fd, res->connectors[i]);
		if (!conn) {
			fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", i, res->connectors[i], errno);
			continue;
		}

		/* create a device structure */
		// dev = malloc(sizeof(*dev));
     	dev = (struct drmdisplay_dev *)malloc(sizeof(struct drmdisplay_dev));
		memset(dev, 0, sizeof(*dev));
		dev->conn = conn->connector_id;

		/* call helper function to prepare this connector */
		ret = drmdisplay_setup_dev(fd, res, conn, dev);
		if (ret) {
			if (ret != -ENOENT) {
				errno = -ret;
				fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", i, res->connectors[i], errno);
			}
			free(dev);
			drmModeFreeConnector(conn);
			continue;
		}

		/* free connector data and link device into global list */
		drmModeFreeConnector(conn);
		dev->next = drmdisplay_list;
		drmdisplay_list = dev;
	}

	/* free resources again */
	drmModeFreeResources(res);
	return 0;
}


static int drmdisplay_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, struct drmdisplay_dev *dev)
{
	int ret;

	/* check if a monitor is connected */
	if (conn->connection != DRM_MODE_CONNECTED) {
		fprintf(stderr, "ignoring unused connector %u\n", conn->connector_id);
		return -ENOENT;
	}

	/* check if there is at least one valid mode */
	if (conn->count_modes == 0) {
		fprintf(stderr, "no valid mode for connector %u\n", conn->connector_id);
		return -EFAULT;
	}

	/* copy the mode information into our device structure */
	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
	dev->bufs[0].width = conn->modes[0].hdisplay;
	dev->bufs[0].height = conn->modes[0].vdisplay;
	dev->bufs[1].width = conn->modes[0].hdisplay;
	dev->bufs[1].height = conn->modes[0].vdisplay;
	fprintf(stderr, "mode for connector %u is %ux%u\n", conn->connector_id, dev->bufs[0].width, dev->bufs[0].height);

	/* find a crtc for this connector */
	ret = drmdisplay_find_crtc(fd, res, conn, dev);
	if (ret) {
		fprintf(stderr, "no valid crtc for connector %u\n", conn->connector_id);
		return ret;
	}

	/* create a framebuffer #1 for this CRTC */
	ret = drmdisplay_create_fb(fd, &dev->bufs[0]);
	if (ret) {
		fprintf(stderr, "cannot create framebuffer for connector %u\n", conn->connector_id);
		return ret;
	}

	/* create a framebuffer #2 for this CRTC */
	ret = drmdisplay_create_fb(fd, &dev->bufs[1]);
	if (ret) {
		fprintf(stderr, "cannot create framebuffer for connector %u\n", conn->connector_id);
		drmdisplay_destory_fb(fd,&dev->bufs[0]);
		return ret;
	}

	return 0;
}

static int drmdisplay_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct drmdisplay_dev *dev)
{
	drmModeEncoder *enc;
	int i, j;
	int32_t crtc;
	struct drmdisplay_dev *iter;

	/* first try the currently conected encoder+crtc */
	if (conn->encoder_id)
		enc = drmModeGetEncoder(fd, conn->encoder_id);
	else
		enc = NULL;

	if (enc) {
		if (enc->crtc_id) {
			crtc = enc->crtc_id;
			for (iter = drmdisplay_list; iter; iter = iter->next) {
				if (iter->crtc == crtc) {
					crtc = -1;
					break;
				}
			}

			if (crtc >= 0) {
				drmModeFreeEncoder(enc);
				dev->crtc = crtc;
				return 0;
			}
		}

		drmModeFreeEncoder(enc);
	}

	/* If the connector is not currently bound to an encoder or if the
	 * encoder+crtc is already used by another connector (actually unlikely
	 * but lets be safe), iterate all other available encoders to find a
	 * matching CRTC. */
	for (i = 0; i < conn->count_encoders; ++i) {
		enc = drmModeGetEncoder(fd, conn->encoders[i]);
		if (!enc) {
			fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", i, conn->encoders[i], errno);
			continue;
		}

		/* iterate all global CRTCs */
		for (j = 0; j < res->count_crtcs; ++j) {
			/* check whether this CRTC works with the encoder */
			if (!(enc->possible_crtcs & (1 << j)))
				continue;

			/* check that no other device already uses this CRTC */
			crtc = res->crtcs[j];
			for (iter = drmdisplay_list; iter; iter = iter->next) {
				if (iter->crtc == crtc) {
					crtc = -1;
					break;
				}
			}

			/* we have found a CRTC, so save it and return */
			if (crtc >= 0) {
				drmModeFreeEncoder(enc);
				dev->crtc = crtc;
				return 0;
			}
		}

		drmModeFreeEncoder(enc);
	}

	fprintf(stderr, "cannot find suitable CRTC for connector %u\n",conn->connector_id);
	return -ENOENT;
}

static int drmdisplay_create_fb(int fd, struct drmdisplay_buf *buf)
{
	struct drm_mode_create_dumb creq;
	struct drm_mode_destroy_dumb dreq;
	struct drm_mode_map_dumb mreq;
	int ret;

    /* create dumb buffer */
    memset(&creq, 0, sizeof(creq));

    creq.width = buf->width;
    creq.height = buf->height;
    creq.bpp = 32;

	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
	if (ret < 0) {
		fprintf(stderr, "cannot create dumb buffer (%d): %m\n",errno);
		return -errno;
	}

	buf->stride = creq.pitch;
	buf->size = creq.size;
	buf->handle = creq.handle;

    /* create framebuffer object for the dumb-buffer */
	ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride,
			   buf->handle, &buf->fb);
	if (ret) {
		fprintf(stderr, "cannot create framebuffer (%d): %m\n",errno);
		ret = -errno;
		memset(&dreq, 0, sizeof(dreq));
		dreq.handle = buf->handle;
		drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
		return ret;
	}

	/* prepare buffer for memory mapping */
	memset(&mreq, 0, sizeof(mreq));
	mreq.handle = buf->handle;
	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
	if (ret) {
		fprintf(stderr, "cannot map dumb buffer (%d): %m\n", errno);
		ret = -errno;
		drmModeRmFB(fd, buf->fb);
		return ret;
	}

	/* perform actual memory mapping */
	// buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);
	buf->map = static_cast<uint8_t *>(mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset));
	if (buf->map == MAP_FAILED) {
		fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", errno);
		ret = -errno;
		drmModeRmFB(fd, buf->fb);
		return ret;
	}

	/* clear the framebuffer to 0 */
	memset(buf->map, 0, buf->size);

	return 0;
}


/*
 * drmdisplay_destory_fb() is a new function. It does exactly the reverse of
 * drmdisplay_create_fb() and destroys a single framebuffer. The modeset.c example
 * used to do this directly in modeset_cleanup().
 * We simply unmap the buffer, remove the drm-FB and destroy the memory buffer.
 */
static void drmdisplay_destory_fb(int fd, struct drmdisplay_buf *buf)
{
	struct drm_mode_destroy_dumb dreq;

	/* unmap buffer */
	munmap(buf->map, buf->size);

	/* delete framebuffer */
	drmModeRmFB(fd, buf->fb);

	/* delete dumb buffer */
	memset(&dreq, 0, sizeof(dreq));
	dreq.handle = buf->handle;
	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
}



/*
 * modeset_page_flip_event() is a callback-helper for modeset_draw() below.
 * Please see modeset_draw() for more information.
 *
 * Note that this does nothing if the device is currently cleaned up. This
 * allows to wait for outstanding page-flips during cleanup.
 */

static void drmdisplay_page_flip_event(int fd, unsigned int frame,
				    unsigned int sec, unsigned int usec,
				    void *data)
{
	struct drmdisplay_dev *dev =static_cast<drmdisplay_dev *>(data);
	fprintf(stderr, "drmdisplay_page_flip_event sec: %d, usec: %d, frame: %d\n", sec, usec, frame);
	dev->pflip_pending = false;
	if (!dev->cleanup)
		drmdisplay_draw_dev(fd, dev);
}


/*
 * A short helper function to compute a changing color value. No need to
 * understand it.
 */
static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
{
	uint8_t next;

	next = cur + (*up ? 1 : -1) * (rand() % mod);
	if ((*up && next < cur) || (!*up && next > cur)) {
		*up = !*up;
		next = cur;
	}

	return next;
}


/*
 * drmdisplay_draw() is the place where things change. The render-logic is the same
 * and we still draw a solid-color on the whole screen. However, we now have two
 * buffers and need to flip between them.
 *
 * So before drawing into a framebuffer, we need to find the back-buffer.
 * Remember, dev->font_buf is the index of the front buffer, so
 * dev->front_buf ^ 1 is the index of the back buffer. We simply use
 * dev->bufs[dev->front_buf ^ 1] to get the back-buffer and draw into it.
 *
 * After we finished drawing, we need to flip the buffers. We do this with the
 * same call as we initially set the CRTC: drmModeSetCrtc(). However, we now
 * pass the back-buffer as new framebuffer as we want to flip them.
 * The only thing left to do is to change the dev->front_buf index to point to
 * the new back-buffer (which was previously the front buffer).
 * We then sleep for a short time period and start drawing again.
 *
 * If you run this example, you will notice that there is almost no flickering,
 * anymore. The buffers are now swapped as a whole so each new frame shows
 * always the whole new image. If you look carefully, you will notice that the
 * modeset.c example showed many screen corruptions during redraw-cycles.
 *
 * However, this example is still not perfect. Imagine the display-controller is
 * currently scanning out a new image and we call drmModeSetCrtc()
 * simultaneously. It will then have the same effect as if we used a single
 * buffer and we get some tearing. But, the chance that this happens is a lot
 * less likely as with a single-buffer. This is because there is a long period
 * between each frame called vertical-blank where the display-controller does
 * not perform a scanout. If we swap the buffers in this period, we have the
 * guarantee that there will be no tearing. See the modeset-vsync.c example if
 * you want to know how you can guarantee that the swap takes place at a
 * vertical-sync.
 */

static void drmdisplay_draw(int fd)
{
	int ret;
	fd_set fds;
	time_t start, cur;
	struct timeval v;
	drmEventContext ev;
	struct drmdisplay_dev *iter;
	int m =0;

	/* init variables */
	srand(time(&start));
	FD_ZERO(&fds);
	memset(&v, 0, sizeof(v));
	memset(&ev, 0, sizeof(ev));

	ev.version = DRM_EVENT_CONTEXT_VERSION;
	ev.page_flip_handler = drmdisplay_page_flip_event;

	/* redraw all outputs */
	for ( iter = drmdisplay_list; iter; iter = iter->next)
	{
		iter->r = rand() % 0xff;
		iter->g = rand() % 0xff;
		iter->b = rand() % 0xff;
		iter->r_up = iter->g_up = iter->b_up = true;
		fprintf(stderr, "drmdisplay_list: \n");
		drmdisplay_draw_dev(fd, iter);
	}

	/* wait 5s for VBLANK or input events */
	while (time(&cur) < start + 5)
	{
		FD_SET(0, &fds);
		FD_SET(fd, &fds);

		v.tv_sec = start + 5 - cur;

		ret = select(fd + 1, &fds, NULL, NULL, &v);
		if (ret < 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)) {
			fprintf(stderr, "drmHandleEvent: %d\n", ++m);
			drmHandleEvent(fd, &ev);
		}
	}
}


/*
 * modeset_draw_dev() is a new function that redraws the screen of a single
 * output. It takes the DRM-fd and the output devices as arguments, redraws a
 * new frame and schedules the page-flip for the next vsync.
 *
 * This function does the same as modeset_draw() did in the previous examples
 * but only for a single output device now.
 * After we are done rendering a frame, we have to swap the buffers. Instead of
 * calling drmModeSetCrtc() as we did previously, we now want to schedule this
 * page-flip for the next vertical-blank (vblank). We use drmModePageFlip() for
 * this. It takes the CRTC-id and FB-id and will asynchronously swap the buffers
 * when the next vblank occurs. Note that this is done by the kernel, so neither
 * a thread is started nor any other magic is done in libdrm.
 * The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a
 * page-flip event on the DRM-fd when the page-flip happened. The last argument
 * is a data-pointer that is returned with this event.
 * If we wouldn't pass this flag, we would not get notified when the page-flip
 * happened.
 *
 * Note: If you called drmModePageFlip() and directly call it again, it will
 * return EBUSY if the page-flip hasn't happened in between. So you almost
 * always want to pass DRM_MODE_PAGE_FLIP_EVENT to get notified when the
 * page-flip happens so you know when to render the next frame.
 * If you scheduled a page-flip but call drmModeSetCrtc() before the next
 * vblank, then the scheduled page-flip will become a no-op. However, you will
 * still get notified when it happens and you still cannot call
 * drmModePageFlip() again until it finished. So to sum it up: there is no way
 * to effectively cancel a page-flip.
 *
 * If you wonder why drmModePageFlip() takes fewer arguments than
 * drmModeSetCrtc(), then you should take into account, that drmModePageFlip()
 * reuses the arguments from drmModeSetCrtc(). So things like connector-ids,
 * x/y-offsets and so on have to be set via drmModeSetCrtc() first before you
 * can use drmModePageFlip()! We do this in main() as all the previous examples
 * did, too.
 */

static void drmdisplay_draw_dev(int fd, struct drmdisplay_dev *dev)
{
	struct drmdisplay_buf *buf;
	unsigned int j, k, off;
	int ret;

	dev->r = next_color(&dev->r_up, dev->r, 20);
	dev->g = next_color(&dev->g_up, dev->g, 10);
	dev->b = next_color(&dev->b_up, dev->b, 5);

	buf = &dev->bufs[dev->front_buf ^ 1];
	for (j = 0; j < buf->height; ++j) {
		for (k = 0; k < buf->width; ++k) {
			off = buf->stride * j + k * 4;
			*(uint32_t*)&buf->map[off] =
				     (dev->r << 16) | (dev->g << 8) | dev->b;
		}
	}

	ret = drmModePageFlip(fd, dev->crtc, buf->fb,
			      DRM_MODE_PAGE_FLIP_EVENT, dev);
	if (ret) {
		fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",
			dev->conn, errno);
	} else {
		dev->front_buf ^= 1;
		dev->pflip_pending = true;
	}
}

static void drmdisplay_cleanup(int fd)
{
	struct drmdisplay_dev *iter;
	drmEventContext ev;
	int ret;	


	/* init variables */
	memset(&ev, 0, sizeof(ev));
	ev.version = DRM_EVENT_CONTEXT_VERSION;
	ev.page_flip_handler = drmdisplay_page_flip_event;

	while (drmdisplay_list) {
		/* remove from global list */
		iter = drmdisplay_list;
		drmdisplay_list = iter->next;

		/* if a pageflip is pending, wait for it to complete */
		iter->cleanup = true;
		fprintf(stderr, "wait for pending page-flip to complete...\n");
		while (iter->pflip_pending) {
			ret = drmHandleEvent(fd, &ev);
			if (ret)
				break;
		}

		/* restore saved CRTC configuration */
		drmModeSetCrtc(fd,
			       iter->saved_crtc->crtc_id,
			       iter->saved_crtc->buffer_id,
			       iter->saved_crtc->x,
			       iter->saved_crtc->y,
			       &iter->conn,
			       1,
			       &iter->saved_crtc->mode);
		drmModeFreeCrtc(iter->saved_crtc);

		/* destroy framebuffers */
		drmdisplay_destory_fb(fd, &iter->bufs[1]);
		drmdisplay_destory_fb(fd, &iter->bufs[0]);

		/* free allocated memory */
		free(iter);
	}
}

int main() {
    fprintf(stderr, "drmdisplay starting\n");
	int ret, fd;
	const char *card;
	struct drmdisplay_dev *iter;
	struct drmdisplay_buf *buf;

    card = "/dev/dri/card2";

	/* open the DRM device */
	ret = drmdisplay_open(&fd, card);
	if (ret) {
		fprintf(stderr, "drmdisplay modeset_open fail\n");
		if (ret) {
			errno = -ret;
			fprintf(stderr, "modeset failed with error %d: %m\n", errno);
		} else {
			fprintf(stderr, "exiting\n");
		}
		return ret;
	}
	/* prepare all connectors and CRTCs */
	ret = drmdisplay_prepare(fd);
	if (ret) {
		fprintf(stderr, "drmdisplay drmdisplay_prepare fail\n");
		close(fd);
		return ret;
	}
	/* perform actual modesetting on each found connector+CRTC */
	for (iter = drmdisplay_list; iter; iter = iter->next) {
		iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
		buf = &iter->bufs[iter->front_buf];
		ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,
				     &iter->conn, 1, &iter->mode);
		if (ret)
			fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", iter->conn, errno);
	}

	/* draw some colors for 5seconds */
	drmdisplay_draw(fd);

	/* cleanup everything */
	drmdisplay_cleanup(fd);

	ret = 0;
    fprintf(stderr, "drmdisplay stopped.  Exiting.\n");

    return ret;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值