@@@表示程序中有具体的函数
1️⃣~8️⃣表示不可缺少的步骤(两个6️⃣无7)
目录可用来整理整体思路
简化描述
思维导图
一、1️⃣检查并打开DRM设备:@@@open(&fd, card);
其实直接用open函数即可,这么写健壮性会比较强
modeset_open(int *out, const char *node)
:该函数用于打开DRM设备表示为:@node.成功后,新的fd将存储在@out中。失败,返回一个负的错误代码。
二、准备所有 连接器与CRTC: @@@modeset_prepare(fd);
modeset_prepare(fd)
:该函数接受DRM fd作为参数和然后简单地从设备检索资源信息。
然后迭代所有连接器,并调用其他辅助函数来初始化这个连接器.
若初始化成功,只需添加这个对象作为新设备进入全局modesset设备列表。
Ⅰ、2️⃣检索资源:GetResources()
res =drmModeGetResources(fd);
libdrm
提供drmModeRes
结构,包含所有需要的信息。
可通过drmModeGetResources(fd)
检索它,并通过drmModeFreeResources (res)
释放它。
Ⅱ、遍历所有的连接器:
for (i = 0; i < res->count_connectors; ++i)
①、 3️⃣ 获取每个连接器信息`GetConnector(, )
conn =drmModeGetConnector(fd, res->connectors[i])
可获取连接器(connector)的详细信息。
显卡上的物理连接器称为“连接器”(connector).可插入显示器并可控制其显示内容。是一个抽象的数据结构,代表连接的显示设备,从Connector中可得到当前物理连接的输出设备相关的信息 ,如:连接状态,EDID数据,DPMS状态、支持的视频模式等。
②、创建设备结构:
dev = malloc(sizeof(*dev))
memset(dev, 0, sizeof(*dev));
dev->conn = conn->connector_id;
③、连接器的准备:@@@setup_dev
modeset_setup_dev(fd, res, conn, dev);
返回-ENOENT如果连接器是当前未使用且未插入显示器。可忽略这个连接器。
modeset_setup_dev(int fd, drmModeRes _res, drmModeConnector _conn, struct modeset_dev *dev)
现在我们深入研究如何设置单个连接器。如前所述,我们首先需要检查几件事:
1.若连接器目前未使用,即没有插入监视器,我们可以忽略它。
2.我们必须找到一个合适的分辨率(resolution)和刷新率(refresh-rate)
这一切都是可在每个crtc的drmModeModeInfo结构保存。我们只是使用第一种模式。
这总是与模式最高的分辨率。
但是,在实际的应用程序中应该进行更复杂的模式选择。
3.我们需要找到一个可以驱动这个连接器的CRTC。CRTC是每个显卡的内部资源。
crtc的数量控制着可以独立控制的连接器的数量。也就是说,一个显卡可能比crtc有更
多的连接器,这意味着不是所有的监视器都可以被独立控制。
如果监视器显示相同的内容,实际上可以通过一个CRTC控制多个连接器。但是,我
们在这里不使用它。
把连接器想象成连接监视器和crtc是管理哪个数据进入哪个管道的控制器。如果
管道比crtc多,我们就不能同时控制所有管道。
4.我们需要为这个连接器创建一个framebuffer。framebuffer是一个我们可以写入
XRGB32数据的内存缓冲区。因此,我们使用它来呈现我们的图形,然后CRTC可以将数据
从帧缓冲区扫描到监视器上。
(1)检查是否有显示器连接(增加健壮性)
if (conn->connection != DRM_MODE_CONNECTED)
(2)检查是否至少有一个有效的模式(增加健壮性)
if (conn->count_modes == 0)
(3)复制模式信息到我们的设备结构
memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
dev->width = conn->modes[0].hdisplay;
dev->height = conn->modes[0].vdisplay;
(4)为这个连接器找到一个CRTC@@@find_crtc
modeset_find_crtc(fd, res, conn, dev);
该函数试图为给定的连接器找到合适的CRTC。实际上,我们必须再引入一个DRM对象来让这个问题更加清楚:编码器(Encoders
)。
编码器(Encoders
)帮助CRTC
将数据从framebuffer
转换为可用于所选连接器(connecter
)的正确格式。我们不需要了解更多的这些转换来使用它。但是,您必须知道每个连接器(connecter
)都有一个它可以使用的编码器(Encoders
)的有限列表。每个编码器(Encoders
)只能与有限的crtc
列表一起工作。所以我们要做的是尝试每一个编码器(Encoders
)是可用的,并寻找一个CRTC
,这个编码器(Encoders
)可以工作。如果我们找到了第一个可行的组合,我们会很高兴地把它写入@dev结构中。
但在迭代所有可用的编码器(Encoders
)之前,我们首先在连接器(connecter
)上尝试当前活动的encoder+crtc
,以避免完整的模式集。且在我们使用CRTC
之前,我们必须确保没有其他设备(我们之前设置的设备)已经在使用这个CRTC。请记住,每个CRTC
只能驱动一个连接器(connecter
)!因此,我们只需遍历以前设置设备的“modeset_list”,并检查这个CRTC以前是否使用过。否则,我们继续使用下一个CRTC/Encoder
组合。
1.首先尝试当前连接的编码器+crtc
enc = drmModeGetEncoder(fd, conn->encoder_id);
2.若没绑定到编码器,或已被使用(不太可能),迭代其他可用编码器匹配crtc
(5)为CRTC创建一个framebuffer@@@create_fb
modeset_create_fb(fd, dev);
在我们找到crtc+连接器+模式组合
后,我们需要实际创建一个合适的框架缓冲区,我们可以使用它。实际上有两种方法可以做到这一点:
方法一:我们可以创建一个所谓的“哑缓冲区(dumb buffer)
”。这是一个我们可以通过mmap()
内存映射的缓冲区,每个驱动程序都支持它。我们可以使用它在CPU上进行非加速的软件渲染。
方法二:我们可以使用libgbm
创建用于硬件加速的缓冲区。libgbm
是一个抽象层,它为每个可用的DRM驱动程序创建这些缓冲区。由于没有通用API,每个驱动程序都提供了自己的方式来创建这些缓冲区。然后我们可以使用这样的缓冲区来创建OpenGL
上下文与mesa3D
库。
我们在这里使用第一种解决方案,因为它简单得多,而且不需要任何外部库。然而,如果你想通过OpenGL使用硬件加速,实际上用libgbm
和libEGL
创建缓冲区是相当容易的。但这超出了本文的讨论范围。
所以我们要做的是从驱动程序请求一个新的哑缓冲区(dumb buffer
)。我们指定的大小与我们为连接器选择的当前模式相同。然后我们请求驱动程序为内存映射准备这个缓冲区。之后,我们执行实际的mmap()
调用。所以我们现在可以通过dev->map
内存map直接访问framebuffer
内存。
1.4️⃣创建dumb buffer :drmIoctl(,,)
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
2.5️⃣为dumb-buffer创建framebuffer对象:drmModeAddFB(, , , , , , , );
drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride, dev->handle, &dev->fb);
3.6️⃣为内存映射准备缓冲区:drmIoctl(,,)
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
4.6️⃣执行实际的内存映射:mmap(,,,,,)
mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);
5.将framebuffer
清除为0:
memset(dev->map, 0, dev->size);
④、释放 连接器数据和链接设备到全局列表:
drmModeFreeConnector(conn);
dev->next = modeset_list;
modeset_list = dev;
Ⅲ、释放资源:
drmModeFreeResources(res);
三、对每个找到的连接器+CRTC执行实际的模式设置(modesset)
for (iter = modeset_list; iter; iter = iter->next) {
iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0, &iter->conn, 1, &iter->mode);
四、画一些颜色5秒:@@@modeset_draw();
modeset_draw()
:在所有已配置的framebuffer
中绘制纯色。每100毫秒,颜色就会变化为一种稍微不同的颜色,所以我们会得到某种平滑变化的颜色梯度。
颜色计算可以忽略,因为它很无聊。有趣的是遍历”modeset_list
“然后遍历所有的行和宽。然后我们将每个像素分别设置为当前的颜色。
我们会在每次重划后100毫秒睡觉时这样做50次。这使得50*100ms = 5000ms = 5s,所以需要5秒才能完成这个循环。
请注意,我们直接在framebuffer
中绘制。这意味着当我们重新绘制屏幕时,当显示器刷新时,您将看到闪烁。为了避免这种情况,您需要使用两个帧缓冲区和对drmModeSetCrtc(
)的调用来在两个缓冲区之间进行切换。
你也可以使用drmModePageFlip()
来做垂直同步的页面翻转。但这超出了本文的讨论范围。
五、清理所有的:@@@modeset_cleanup(fd);
Ⅰ、从全局列表中删除
iter = modeset_list;
modeset_list = iter->next;
Ⅱ、8️⃣恢复保存的CRTC配置;drmModeSetCrtc(,,,,,,,)
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);
Ⅲ、unmap buffer
munmap(iter->map, iter->size);
Ⅳ、删除framebuffer
drmModeRmFB(fd, iter->fb);
Ⅴ、删除dumb buffer
memset(&dreq, 0, sizeof(dreq));
Ⅵ、释放分配的内存:
free(iter);
评论(0)
您还未登录,请登录后发表或查看评论