要绕过传统的窗口系统(如X或Wayland),直接通过DRM(Direct Rendering Manager)控制和管理显示设备绘制图像,并显示在屏幕上,通常需要以下步骤:
1. 打开 DRM 设备
首先,程序通过 open() 函数打开 DRM 设备文件 (/dev/dri/card0),并获得一个文件描述符 drm_fd。这个描述符用于后续的所有 DRM 操作。
2. 获取 DRM 资源
使用 drmModeGetResources() 获取当前显示设备的资源信息,包括连接器(Connectors)、编码器(Encoders)、和 CRTC(Cathode Ray Tube Controllers)的列表。这些资源是显示图像的基础。
3. 查找有效的连接器
通过遍历获取的连接器列表,查找一个已经连接并可用的显示器连接器(即 DRM_MODE_CONNECTED 状态的连接器)。连接器代表显示输出接口,如 HDMI、VGA 等。
4. 获取连接器的显示模式
在找到一个有效的连接器后,从该连接器中提取显示模式(如分辨率和刷新率),这是后续设置帧缓冲区所需的参数。
5. 获取编码器和 CRTC
根据连接器的 encoder_id 获取对应的编码器,编码器将连接器与 CRTC 关联起来。再通过编码器的 crtc_id 获取 CRTC,CRTC 是负责实际控制图像扫描输出的硬件模块。
6. 创建帧缓冲区
使用 drm_mode_create_dumb 创建一个 “dumb buffer”(简单的帧缓冲区),这个缓冲区将被用于存储将要显示的图像数据。然后使用 drmModeAddFB 将这个缓冲区注册为 DRM 的帧缓冲区对象,并获取一个帧缓冲区 ID (fb_id),以供后续操作。
7. 映射帧缓冲区到用户空间
通过 mmap 函数将创建的帧缓冲区映射到用户空间内存中,从而可以直接操作它来更新显示内容。
8. 绘制图像
使用 std::fill 将帧缓冲区内存填充为红色(或其他颜色),这相当于绘制了一个纯色图像到屏幕上。
9. 设置 CRTC 并显示图像
使用 drmModeSetCrtc 将帧缓冲区与 CRTC 绑定,指定要显示的内容及其在屏幕上的位置,从而将绘制的图像输出到显示器上。
10. 等待和清理
在显示图像一段时间后(比如 5 秒),程序清理所分配的资源,包括解除内存映射、释放 CRTC、编码器、连接器和资源信息,并关闭 DRM 设备。
总结
-
打开设备: 获取 DRM 文件描述符。
-
获取资源: 获取连接器、编码器和 CRTC 的信息。
-
创建和映射帧缓冲区: 创建用于图像存储的帧缓冲区,并映射到用户空间。
-
绘制和显示: 填充帧缓冲区并通过 CRTC 显示到屏幕上。
-
清理资源: 释放分配的所有资源,确保程序干净退出。
代码示例:简单的 DRM 程序
下面是一个完整的简单程序示例,使用 DRM 设置显示模式并绘制一个纯色图像。这个示例会打开 DRM 设备/dev/dri/card0,获取显示资源,设置显示模式,创建帧缓冲区,并绘制一个红色屏幕。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <drm/drm.h>
#include <drm/drm_mode.h>
int main() {
int drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
if (drm_fd < 0) {
perror("Cannot open /dev/dri/card0");
return -1;
}
// 获取 DRM 资源
drmModeRes *resources = drmModeGetResources(drm_fd);
if (!resources) {
perror("drmModeGetResources failed");
close(drm_fd);
return -1;
}
// 获取第一个连接器
drmModeConnector *connector = NULL;
for (int i = 0; i < resources->count_connectors; i++) {
connector = drmModeGetConnector(drm_fd, resources->connectors[i]);
if (connector->connection == DRM_MODE_CONNECTED) {
break;
}
drmModeFreeConnector(connector);
connector = NULL;
}
if (!connector) {
fprintf(stderr, "No connected connector found\n");
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
// 获取第一个显示模式
drmModeModeInfo mode = connector->modes[0];
// 获取第一个 encoder
drmModeEncoder *encoder = drmModeGetEncoder(drm_fd, connector->encoder_id);
if (!encoder) {
perror("drmModeGetEncoder failed");
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
// 获取 CRTC
drmModeCrtc *crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id);
// 创建 framebuffer
uint32_t fb_id;
uint32_t width = mode.hdisplay;
uint32_t height = mode.vdisplay;
uint32_t pitch = width * 4; // 每行字节数(假设 32 位色深)
uint32_t size = pitch * height;
uint32_t handle;
uint32_t *fb_ptr;
struct drm_mode_create_dumb create_req;
struct drm_mode_map_dumb map_req;
memset(&create_req, 0, sizeof(create_req));
create_req.width = width;
create_req.height = height;
create_req.bpp = 32;
if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) {
perror("drmIoctl DRM_IOCTL_MODE_CREATE_DUMB failed");
drmModeFreeCrtc(crtc);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
handle = create_req.handle;
if (drmModeAddFB(drm_fd, width, height, 24, 32, pitch, handle, &fb_id)) {
perror("drmModeAddFB failed");
drmModeFreeCrtc(crtc);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
memset(&map_req, 0, sizeof(map_req));
map_req.handle = handle;
if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)) {
perror("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed");
drmModeFreeCrtc(crtc);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
fb_ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, map_req.offset);
if (fb_ptr == MAP_FAILED) {
perror("mmap failed");
drmModeFreeCrtc(crtc);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
// 绘制红色背景
for (uint32_t i = 0; i < size / 4; i++) {
fb_ptr[i] = 0x00FF0000; // ABGR, 所以 0x00FF0000 是红色
}
// 设置 CRTC 并显示图像
if (drmModeSetCrtc(drm_fd, crtc->crtc_id, fb_id, 0, 0, &connector->connector_id, 1, &mode)) {
perror("drmModeSetCrtc failed");
munmap(fb_ptr, size);
drmModeFreeCrtc(crtc);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return -1;
}
sleep(5); // 等待 5 秒
// 清理
munmap(fb_ptr, size);
drmModeFreeCrtc(crtc);
drmModeFreeEncoder(encoder);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(drm_fd);
return 0;
}
代码解释
- 初始化 DRM 设备:程序首先使用
open函数打开/dev/dri/card0,以获得 DRM 设备的文件描述符。 - 获取显示资源:使用
drmModeGetResources获取可用的显示资源,包括连接器、编码器和 CRTC 等。 - 设置显示模式:选择一个连接器,获取第一个可用的显示模式,并设置该模式。
- 创建帧缓冲:使用
DRM_IOCTL_MODE_CREATE_DUMB创建一个帧缓冲区,映射到内存中,然后使用drmModeAddFB将其添加为 framebuffer。 - 绘制内容:通过直接操作内存,填充整个帧缓冲区为红色(
0x00FF0000)。 - 显示图像:通过
drmModeSetCrtc函数设置 CRTC 并显示图像。 - 等待和清理:程序显示图像5秒后退出,释放所有资源并关闭设备。
1175

被折叠的 条评论
为什么被折叠?



