刚开始学习,记忆不是很好,容易忘,边学边记,阅读的速度会比较慢,看的会比较仔细。

详细请看:
  David Herrmann’s Blog: Linux DRM Mode-Setting API
  David Herrmann’s Github: drm-howto/modeset.c


modeset-double-buffered.c文件

源代码在上面可获取,原先备注是英文的,我就简单用翻译软件翻译一下,有错请指出:

/
							  		DRM双缓冲
	本例扩展了modeset.c 引入了双缓冲。当在framebuffer中绘制一个新帧时,我们应该总是绘制一
个未使用的缓冲区,而不是前面的缓冲区。如果我们在前面的缓冲区中绘图,那么当显示控制器开始扫描
下一帧时,我们可能已经绘制了一半的帧。因此,我们在屏幕上看到闪烁。
	避免这种情况的技术称为双缓冲。我们有两个framebuffer,一个是用于扫描的前端缓冲区,另一个
是用于绘制操作的后端缓冲区。当一个帧完成时,我们只需交换两个缓冲区。交换并不意味着复制数据,
相反,只交换指向缓冲区的指针。
	在阅读这个文件之前,请先阅读modeset.c,因为大多数函数都是相同的。这里只强调了不同之处。
还要注意,通过遵循这里的方案,可以很容易地实现三重缓冲或任何其他数量的缓冲区。然而,在这个例
子中,我们将缓冲区的数量限制为2,这样更容易理解。
 /
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

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_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);
static int modeset_prepare(int fd);
static void modeset_draw(int fd);
static void modeset_cleanup(int fd);

/
	和前例相同
/
static int modeset_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, "cannot open '%s': %m\n", node);
		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;
}

/
	在前面,我们也使用modeset_dev对象来保存缓冲区信息。
	从技术上讲,我们本可以将它们分开,但为了使示例更简单,我们避免了这样做。
	然而,在本例中,我们需要2个缓冲区。一个后缓冲器和一个前缓冲器。因此,我们引入了一个新的结构
modeset_buf,它包含了与单个缓冲区相关的所有内容。现在,每个设备都获得一个由两个缓冲区组成的数组。
	每个缓冲区由宽度、高度、步幅、大小、句柄、map和fb-id组成。它们的意思和以前一样。
	每个设备还获得一个新的整数字段:front_buf。该字段包含当前用作前端缓冲区/扫描输出缓冲区的缓冲
区索引。在我们的例子中,它可以是0或1。我们可以使用XOR: dev->front_buf ^= dev->front_buf来翻转
它其他的都保持不变。
/

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

struct modeset_dev {
	struct modeset_dev *next;
	unsigned int front_buf;			/新增,该字段包含当前用作前端缓冲区/扫描输出缓冲区的缓冲区索引
	struct modeset_buf bufs[2];
	drmModeModeInfo mode;
	uint32_t conn;
	uint32_t crtc;
	drmModeCrtc *saved_crtc;
};

static struct modeset_dev *modeset_list = NULL;

/
	和前例相同
/
static int modeset_prepare(int fd)
{
	drmModeRes *res;
	drmModeConnector *conn;
	int i;
	struct modeset_dev *dev;
	int ret;

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

	/			遍历所有的连接器			/
	for (i = 0; i < res->count_connectors; ++i) {
		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;
		}

		/			创建设备结构			/
		dev = malloc(sizeof(*dev));
		memset(dev, 0, sizeof(*dev));				/对该段内存内容置0
		dev->conn = conn->connector_id;

		/			调用该函数来准备这个连接器		/
		ret = modeset_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;
		}

		/			释放连接器数据和链接设备到全局列表			/
		drmModeFreeConnector(conn);
		dev->next = modeset_list;
		modeset_list = dev;
	}

	/		再次释放资源			/
	drmModeFreeResources(res);
	return 0;
}

/
	modeset_setup_dev()为单个设备设置所有资源。它基本上保持不变,但有一点改变了:
分配了两个framebuffer,而不是一个。也就是说,我们调用modeset_create_fb()两次。
	还将宽度/高度复制到两个framebuffer中,以便modeset_create_fb()可以使用它们,
而不需要指向modeset_dev的指针。
/

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

	/			检查是否有显示器连接			/
	if (conn->connection != DRM_MODE_CONNECTED) {
		fprintf(stderr, "ignoring unused connector %u\n",conn->connector_id);
		return -ENOENT;
	}

	/			检查是否至少有一个有效的模式		/
	if (conn->count_modes == 0) {
		fprintf(stderr, "no valid mode for connector %u\n",
			conn->connector_id);
		return -EFAULT;
	}

	/		复制模式信息到我们的设备结构和两个缓冲区		/
	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);

	/			为这个连接器找到一个crtc			/
	ret = modeset_find_crtc(fd, res, conn, dev);
	if (ret) {
		fprintf(stderr, "no valid crtc for connector %u\n",conn->connector_id);
		return ret;
	}

	/		为这个CRTC创建framebuffer #1		/
	ret = modeset_create_fb(fd, &dev->bufs[0]);
	if (ret) {
		fprintf(stderr, "cannot create framebuffer for connector %u\n",conn->connector_id);
		return ret;
	}

	/		为这个CRTC创建framebuffer #2		/
	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;
	}

	return 0;
}

/
	和前例相同
/
static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev)
{
	drmModeEncoder *enc;
	int i, j;
	uint32_t crtc;
	struct modeset_dev *iter;

	/		首先尝试当前连接的编码器+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 = modeset_list; iter; iter = iter->next) {
				if (iter->crtc == crtc) {
					crtc = -1;
					break;
				}
			}

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

		drmModeFreeEncoder(enc);
	}

	/
		如果连接器目前没有绑定到一个编码器,或者encoder+crtc已经被另一个连接器使用(实际上不太可能,
		但让我们安全),迭代所有其他可用的编码器来找到匹配的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;
		}

		/			遍历所有crtc 		/
		for (j = 0; j < res->count_crtcs; ++j) {
			if (!(enc->possible_crtcs & (1 << j)))		/检查此CRTC是否与编码器一起工作
				continue;

			/			检查是否有其他设备已经使用此CRTC		/
			crtc = res->crtcs[j];
			for (iter = modeset_list; iter; iter = iter->next) {
				if (iter->crtc == crtc) {
					crtc = -1;
					break;
				}
			}
			/			我们已经找到一个CRTC,所以保存它并返回			/
			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;
}

/
	modeset_create_fb()与以前基本相同。我们现在需要一个作为@buf传递的缓冲区指针,而不是编写modeset_dev的字段。
请注意,buf->宽度和buf->高度是由modeset_setup_dev()初始化的,所以我们可以在这里使用它们。
/

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

	/ 	创建  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;

	/		为dumb-buffer创建framebuffer对象		/
	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;
		goto err_destroy;
	}

	/		为内存映射准备缓冲区		/
	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;
		goto err_fb;
	}

	/		执行实际的内存映射		/
	buf->map = 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;
		goto err_fb;
	}

	/		将framebuffer清除为0		/
	memset(buf->map, 0, buf->size);

	return 0;

err_fb:
	drmModeRmFB(fd, buf->fb);
err_destroy:
	memset(&dreq, 0, sizeof(dreq));
	dreq.handle = buf->handle;
	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
	return ret;
}

/
	modeset_destroy_fb()是一个新函数。它的操作与modeset_create_fb()完全相反,
并破坏单个framebuffer。在modeset_cleanup()中使用了modeset.c示例来直接执行此操作。
我们只需取消对缓冲区的映射,删除drm-FB并销毁内存缓冲区。
/

static void modeset_destroy_fb(int fd, struct modeset_buf *buf)
{
	struct drm_mode_destroy_dumb dreq;

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

	/	 删除 framebuffer 	/
	drmModeRmFB(fd, buf->fb);

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

/
	main()也几乎与以前完全相同。我们只需要改变最初设置crtc的方式。我们现在不使用来自
modeset_dev的缓冲区信息,而是使用dev->bufs[iter->front_buf]来获取当前的front-buffer,
并将这个帧缓冲区用于drmModeSetCrtc()。
/

int main(int argc, char **argv)
{
	int ret, fd;
	const char *card;
	struct modeset_dev *iter;
	struct modeset_buf *buf;

	/			检查打开哪个DRM设备		/
	if (argc > 1)
		card = argv[1];
	else
		card = "/dev/dri/card0";

	fprintf(stderr, "using card '%s'\n", card);

	/		打开DRM设备		/
	ret = modeset_open(&fd, card);
	if (ret)
		goto out_return;

	/		准备好所有连接器和crtc		/
	ret = modeset_prepare(fd);
	if (ret)
		goto out_close;

	/		对每个找到的连接器+CRTC执行实际的modesset		/
	for (iter = modeset_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);
	}
	
	/		画一些颜色5秒		/
	modeset_draw(fd);
	/		清理所有		/
	modeset_cleanup(fd);
	ret = 0;

out_close:
	close(fd);
out_return:
	if (ret) {
		errno = -ret;
		fprintf(stderr, "modeset failed with error %d: %m\n", errno);
	} else {
		fprintf(stderr, "exiting\n");
	}
	return ret;
}

/
	一个简短的辅助函数来计算变化的颜色值。没必要去理解它。
/

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;
}

/
	modeset_draw()是变化的地方。呈现逻辑是一样的,我们仍然在整个屏幕上绘制纯色。然而,我们现在有两个缓冲区,
需要在它们之间切换。
	因此,在绘制到framebuffer之前,我们需要找到back-buffer。记住,dev->font_buf是前缓冲区的索引,
所以dev->front_buf ^ 1是后缓冲区的索引。我们简单地使用dev->bufs[dev->front_buf ^ 1]来获取回缓冲
区并在其中绘图。
	在我们完成绘制之后,我们需要翻转缓冲区。我们使用与最初设置CRTC相同的调用:drmModeSetCrtc()。
然而,我们现在将back-buffer作为新的framebuffer传递,因为我们想要翻转它们。
	剩下要做的唯一一件事是更改dev->front_buf索引,使之指向新的后缓冲区(以前是前缓冲区)。
	然后我们延时了一小会儿,又开始画画。
	如果您运行这个示例,您将注意到几乎没有闪烁了。缓冲区现在作为一个整体交换,
所以每个新帧总是显示整个新图像。如果仔细观察,您会注意到modeset.c示例在重绘制周期中显示了许多屏幕损坏。
	然而,这个例子仍然不完美。假设显示控制器正在扫描一个新图像,我们同时调用drmModeSetCrtc()。这就和我们使用
单一缓冲液时产生的撕裂效果是一样的。但是,与使用单个缓冲区相比,发生这种情况的可能性要小得多。这是因为在每一帧
之间有一段很长的时间称为vertical-blank,其中显示控制器不执行扫描。如果我们在这段时间内交换缓冲区,我们就可以
保证不会发生撕裂。如果你想知道如何保证交换在垂直同步中发生,请参阅modeset-vsync.c示例。
/

static void modeset_draw(int fd)
{
	uint8_t r, g, b;
	bool r_up, g_up, b_up;
	unsigned int i, j, k, off;
	struct modeset_dev *iter;
	struct modeset_buf *buf;
	int ret;

	srand(time(NULL));
	r = rand() % 0xff;
	g = rand() % 0xff;
	b = rand() % 0xff;
	r_up = g_up = b_up = true;

	for (i = 0; i < 50; ++i) {
		r = next_color(&r_up, r, 20);
		g = next_color(&g_up, g, 10);
		b = next_color(&b_up, b, 5);
		for (iter = modeset_list; iter; iter = iter->next) {
			buf = &iter->bufs[iter->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] = (r << 16) | (g << 8) | b;
				}
			}
			ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,&iter->conn, 1, &iter->mode);
			if (ret)
				fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",iter->conn, errno);
			else
				iter->front_buf ^= 1;
		}
		usleep(100000);
	}
}

/
	modeset_cleanup()保持与以前相同。但是它现在调用modeset_destroy_fb(),而不是直接访问framebuffer
/

static void modeset_cleanup(int fd)
{
	struct modeset_dev *iter;

	while (modeset_list) {
		/		从全局列表中删除		/
		iter = modeset_list;
		modeset_list = iter->next;

		/		恢复保存的CRTC配置		/
		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);

		/		销毁framebuffers 		/
		modeset_destroy_fb(fd, &iter->bufs[1]);
		modeset_destroy_fb(fd, &iter->bufs[0]);

		/		释放存储器配置		/
		free(iter);
	}
}

/
	这是对基本模型设置示例的一个非常简短的扩展,该示例展示了如何实现双缓冲。双缓冲是任何图形
应用程序事实上的标准,因此任何其他示例都将基于此。理解其背后的思想是很重要的,因为与modeset.c
相比,代码非常简单和简短。
	双缓冲并不能解决所有问题。垂直同步的页面翻转解决了大多数仍然会发生的问题,但它本身也有问题
(请参阅modeset-vsync.c进行讨论)。
	如果你想要更多的代码,我可以推荐你阅读下面的源代码:
	-plymouth(它使用了像这个例子一样的哑缓冲区;非常容易理解)
	-kmscon(使用libuterm来做这个)
	-wayland(非常复杂的DRM渲染器;很难完全理解,因为它使用更复杂的技术,如DRM飞机)
	-xserver(很难理解,因为它被分割在很多文件/项目中)
	欢迎任何反馈。您可以自由地将这些代码用于自己的文档或项目。
/