[psplash,RFC,14/14] psplash-drm.c: Implement double buffering

Message ID 20220425075954.10427-15-vasyl.vavrychuk@opensynergy.com
State New
Headers show
Series Implement DRM backend | expand

Commit Message

Vasyl Vavrychuk April 25, 2022, 7:59 a.m. UTC
Based on
https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset-double-buffered.c

Signed-off-by: Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>
---
 psplash-drm.c | 176 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 140 insertions(+), 36 deletions(-)

Patch

diff --git a/psplash-drm.c b/psplash-drm.c
index 5e56286..fcb7507 100644
--- a/psplash-drm.c
+++ b/psplash-drm.c
@@ -29,6 +29,7 @@ 
 #include <errno.h>
 #include <fcntl.h>
 #include <stdint.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -41,10 +42,12 @@ 
 
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 
+struct modeset_buf;
 struct modeset_dev;
 static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
 			     struct modeset_dev *dev);
-static int modeset_create_fb(int fd, struct modeset_dev *dev);
+static int modeset_create_fb(int fd, struct modeset_buf *buf);
+static void modeset_destroy_fb(int fd, struct modeset_buf *buf);
 static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
 			     struct modeset_dev *dev);
 static int modeset_open(int *out, const char *node);
@@ -144,18 +147,45 @@  static int modeset_open(int *out, const char *node)
  * }
  */
 
-struct modeset_dev {
-	struct modeset_dev *next;
+/*
+ * Previously, we used the modeset_dev objects to hold buffer informations, too.
+ * Technically, we could have split them but avoided this to make the
+ * example simpler.
+ * However, in this example we need 2 buffers. One back buffer and one front
+ * buffer. So we introduce a new structure modeset_buf which contains everything
+ * related to a single buffer. Each device now gets an array of two of these
+ * buffers.
+ * Each buffer consists of width, height, stride, size, handle, map and fb-id.
+ * They have the same meaning as before.
+ *
+ * Each device also gets a new integer field: front_buf. This field contains the
+ * index of the buffer that is currently used as front buffer / scanout buffer.
+ * In our example it can be 0 or 1. We flip it by using XOR:
+ *   dev->front_buf ^= dev->front_buf
+ *
+ * Everything else stays the same.
+ */
 
+struct modeset_buf {
 	uint32_t width;
 	uint32_t height;
 	uint32_t stride;
 	uint32_t size;
 	uint32_t handle;
 	void *map;
+	uint32_t fb;
+};
+
+struct modeset_dev {
+	struct modeset_dev *next;
+
+	uint32_t width;
+	uint32_t height;
+
+	unsigned int front_buf;
+	struct modeset_buf bufs[2];
 
 	drmModeModeInfo mode;
-	uint32_t fb;
 	uint32_t conn;
 	uint32_t crtc;
 	drmModeCrtc *saved_crtc;
@@ -292,10 +322,15 @@  static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
 		return -EFAULT;
 	}
 
-	/* copy the mode information into our device structure */
+	/* copy the mode information into our device structure and into both
+	 * buffers */
 	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
 	dev->width = conn->modes[0].hdisplay;
 	dev->height = conn->modes[0].vdisplay;
+	dev->bufs[0].width = dev->width;
+	dev->bufs[0].height = dev->height;
+	dev->bufs[1].width = dev->width;
+	dev->bufs[1].height = dev->height;
 	fprintf(stderr, "mode for connector %u is %ux%u\n",
 		conn->connector_id, dev->width, dev->height);
 
@@ -307,14 +342,30 @@  static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
 		return ret;
 	}
 
-	/* create a framebuffer for this CRTC */
-	ret = modeset_create_fb(fd, dev);
+	/* create framebuffer #1 for this CRTC */
+	ret = modeset_create_fb(fd, &dev->bufs[0]);
 	if (ret) {
 		fprintf(stderr, "cannot create framebuffer for connector %u\n",
 			conn->connector_id);
 		return ret;
 	}
 
+	/* create framebuffer #2 for this CRTC */
+	ret = modeset_create_fb(fd, &dev->bufs[1]);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer for connector %u\n",
+			conn->connector_id);
+		modeset_destroy_fb(fd, &dev->bufs[0]);
+		return ret;
+	}
+
+	if (dev->bufs[0].size != dev->bufs[1].size) {
+		fprintf(stderr, "front buffer size %" PRIu32 " does not match "
+				"back buffer size %" PRIu32 "\n",
+				dev->bufs[0].size, dev->bufs[1].size);
+		return -1;
+	}
+
 	return 0;
 }
 
@@ -441,7 +492,7 @@  static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
  * memory directly via the dev->map memory map.
  */
 
-static int modeset_create_fb(int fd, struct modeset_dev *dev)
+static int modeset_create_fb(int fd, struct modeset_buf *buf)
 {
 	struct drm_mode_create_dumb creq;
 	struct drm_mode_destroy_dumb dreq;
@@ -450,8 +501,8 @@  static int modeset_create_fb(int fd, struct modeset_dev *dev)
 
 	/* create dumb buffer */
 	memset(&creq, 0, sizeof(creq));
-	creq.width = dev->width;
-	creq.height = dev->height;
+	creq.width = buf->width;
+	creq.height = buf->height;
 	creq.bpp = 32;
 	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
 	if (ret < 0) {
@@ -459,13 +510,13 @@  static int modeset_create_fb(int fd, struct modeset_dev *dev)
 			errno);
 		return -errno;
 	}
-	dev->stride = creq.pitch;
-	dev->size = creq.size;
-	dev->handle = creq.handle;
+	buf->stride = creq.pitch;
+	buf->size = creq.size;
+	buf->handle = creq.handle;
 
 	/* create framebuffer object for the dumb-buffer */
-	ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride,
-			   dev->handle, &dev->fb);
+	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);
@@ -475,7 +526,7 @@  static int modeset_create_fb(int fd, struct modeset_dev *dev)
 
 	/* prepare buffer for memory mapping */
 	memset(&mreq, 0, sizeof(mreq));
-	mreq.handle = dev->handle;
+	mreq.handle = buf->handle;
 	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
 	if (ret) {
 		fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
@@ -485,9 +536,9 @@  static int modeset_create_fb(int fd, struct modeset_dev *dev)
 	}
 
 	/* perform actual memory mapping */
-	dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+	buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
 		        fd, mreq.offset);
-	if (dev->map == MAP_FAILED) {
+	if (buf->map == MAP_FAILED) {
 		fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
 			errno);
 		ret = -errno;
@@ -495,23 +546,73 @@  static int modeset_create_fb(int fd, struct modeset_dev *dev)
 	}
 
 	/* clear the framebuffer to 0 */
-	memset(dev->map, 0, dev->size);
+	memset(buf->map, 0, buf->size);
 
 	return 0;
 
 err_fb:
-	drmModeRmFB(fd, dev->fb);
+	drmModeRmFB(fd, buf->fb);
 err_destroy:
 	memset(&dreq, 0, sizeof(dreq));
-	dreq.handle = dev->handle;
+	dreq.handle = buf->handle;
 	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
 	return ret;
 }
 
+/*
+ * modeset_destroy_fb() is a new function. It does exactly the reverse of
+ * modeset_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 modeset_destroy_fb(int fd, struct modeset_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);
+}
+
 static void psplash_drm_flip(PSplashCanvas *canvas, int sync)
 {
-	(void)canvas;
+	PSplashDRM *drm = canvas->priv;
+	struct modeset_buf *buf;
+	int ret;
+
 	(void)sync;
+
+	/* pick a back buffer */
+	buf = &modeset_list->bufs[modeset_list->front_buf ^ 1];
+
+	/* set back buffer as a front buffer */
+	ret = drmModeSetCrtc(drm->fd, modeset_list->crtc, buf->fb, 0, 0,
+			&modeset_list->conn, 1, &modeset_list->mode);
+	if (ret) {
+		fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",
+			modeset_list->conn, errno);
+		return;
+	}
+
+	/* update front buffer index */
+	modeset_list->front_buf ^= 1;
+
+	/* update back buffer pointer */
+	drm->canvas.data = modeset_list->bufs[modeset_list->front_buf ^ 1].map;
+
+	/* Sync new front to new back when requested */
+	if (sync)
+		memcpy(modeset_list->bufs[modeset_list->front_buf ^ 1].map,
+			modeset_list->bufs[modeset_list->front_buf].map,
+			modeset_list->bufs[0].size);
 }
 
 /*
@@ -555,6 +656,7 @@  PSplashDRM* psplash_drm_new(int angle, int dev_id)
 	int ret;
 	char card[] = "/dev/dri/card0";
 	struct modeset_dev *iter;
+	struct modeset_buf *buf;
 
 	if ((drm = malloc(sizeof(*drm))) == NULL) {
 		perror("malloc");
@@ -583,18 +685,28 @@  PSplashDRM* psplash_drm_new(int angle, int dev_id)
 	/* perform actual modesetting on each found connector+CRTC */
 	for (iter = modeset_list; iter; iter = iter->next) {
 		iter->saved_crtc = drmModeGetCrtc(drm->fd, iter->crtc);
-		ret = drmModeSetCrtc(drm->fd, iter->crtc, iter->fb, 0, 0,
+		buf = &iter->bufs[iter->front_buf];
+		ret = drmModeSetCrtc(drm->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);
 	}
 
-	drm->canvas.data = modeset_list->map;
+	drm->canvas.data = modeset_list->bufs[modeset_list->front_buf ^ 1].map;
 	drm->canvas.width = modeset_list->width;
 	drm->canvas.height = modeset_list->height;
 	drm->canvas.bpp = 32;
-	drm->canvas.stride = modeset_list->stride;
+
+	if (modeset_list->bufs[0].stride != modeset_list->bufs[1].stride) {
+		fprintf(stderr, "front buffer stride %" PRIu32 " does not match"
+				" back buffer stride %" PRIu32 "\n",
+				modeset_list->bufs[0].stride,
+				modeset_list->bufs[1].stride);
+		goto error;
+	}
+	drm->canvas.stride = modeset_list->bufs[0].stride;
+
 	drm->canvas.angle = angle;
 	drm->canvas.rgbmode = RGB888;
 
@@ -614,7 +726,6 @@  error:
 void psplash_drm_destroy(PSplashDRM *drm)
 {
 	struct modeset_dev *iter;
-	struct drm_mode_destroy_dumb dreq;
 
 	if (!drm)
 		return;
@@ -635,16 +746,9 @@  void psplash_drm_destroy(PSplashDRM *drm)
 			       &iter->saved_crtc->mode);
 		drmModeFreeCrtc(iter->saved_crtc);
 
-		/* unmap buffer */
-		munmap(iter->map, iter->size);
-
-		/* delete framebuffer */
-		drmModeRmFB(drm->fd, iter->fb);
-
-		/* delete dumb buffer */
-		memset(&dreq, 0, sizeof(dreq));
-		dreq.handle = iter->handle;
-		drmIoctl(drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+		/* destroy framebuffers */
+		modeset_destroy_fb(drm->fd, &iter->bufs[1]);
+		modeset_destroy_fb(drm->fd, &iter->bufs[0]);
 
 		/* free allocated memory */
 		free(iter);