刚开始学习,记忆不是很好,容易忘,边学边记,阅读的速度会比较慢,看的会比较仔细。
详细请看:
David Herrmann’s Blog: Linux DRM Mode-Setting API
David Herrmann’s Github: drm-howto/modeset.c
modeset-atomic.c文件
源代码在上面可获取,原先备注是英文的,我就简单用翻译软件翻译一下,有错请指出:
/
这个例子扩展了modeset-vsync.c,介绍了平面和原子API。
在扫描过程中,平面可以用于在CRTC帧缓冲区上混合或叠加图像。并不是所有的硬件都提供Planes,
可用Planes的数量也是有限的。如果没有足够的平面或硬件不提供它们,用户应该通过GPU或CPU的组合来
混合或覆盖平面。请注意,这个渲染过程会导致延迟,这是为什么需要快速的现代硬件使用平面的理由。
有三种类型的平面:主平面、光标平面和覆盖平面。为了与遗留用户空间兼容,默认行为是只向用户空间
公开覆盖的平面(我们将在代码中看到我们必须要求接收所有类型的平面)。平面使用的一个很好的例子是:
想象一个静态的桌面屏幕,用户正在移动光标。只有光标在移动。我们不需要每次用户移动光标时计算完整的场景,
我们只需要更新光标平面,它将被主平面上的硬件自动覆盖。在这种情况下不需要软件组合。
但多个Planes使用时出现了同步问题。KMS API不是原子的,所以你必须用不同的IOCTL来更新主平面和覆盖
平面。这可能会导致撕裂以及一些与阻塞相关的问题,所以有人提议使用原子API来解决这些问题。
随着KMS原子API的引入,所有的平面都可以在一个IOCTL中使用drmModeAtomicCommit()进行更新。这可以是异
步的,也可以是完全阻塞的。
本例假设您熟悉modeset-vsync。这里只突出显示了两个文件之间的差异。
/
#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>
#include <drm_fourcc.h>
/
引入了一个新的结构:drm_object。它存储在原子模式集设置和原子页面翻转
(在一个IOCTL中更新所有平面)中使用的特定对象(连接器、CRTC和平面)的属性。
/
struct drm_object {
drmModeObjectProperties *props;
drmModePropertyRes **props_info;
uint32_t id;
};
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_output {
struct modeset_output *next;
unsigned int front_buf;
struct modeset_buf bufs[2];
struct drm_object connector;
struct drm_object crtc;
struct drm_object plane;
drmModeModeInfo mode;
uint32_t mode_blob_id;
uint32_t crtc_index;
bool pflip_pending;
bool cleanup;
uint8_t r, g, b;
bool r_up, g_up, b_up;
};
static struct modeset_output *output_list = NULL;
/
modeset_open()只改变了一点点。现在我们必须设置使用KMS原子API,
并检查设备是否能够处理它。
/
static int modeset_open(int *out, const char *node)
{
int fd, ret;
uint64_t cap;
fd = open(node, O_RDWR | O_CLOEXEC);
if (fd < 0) {
ret = -errno;
fprintf(stderr, "cannot open '%s': %m\n", node);
return ret;
}
/
设置我们想要接收列表中所有planes的类型。这是必须做的,因为,由于遗留的原因,
默认行为是只向用户公开覆盖平面。只有设置了这个,原子API才能工作。
/
ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
if (ret) {
fprintf(stderr, "failed to set universal planes cap, %d\n", ret);
return ret;
}
/
在这里,我们将使用KMS原子API。它应该自动设置DRM_CLIENT_CAP_UNIVERSAL_PLANES,
但是像我们在前面的命令中所做的那样显式地设置它是一种安全的行为。这对于学习也是有好处的。
/
ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
if (ret) {
fprintf(stderr, "failed to set atomic cap, %d", ret);
return ret;
}
//drmGetCap函数用来检验设备是否满足想要的功能。不满足的话则报错。
if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap) < 0 || !cap) {
fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
node);
close(fd);
return -EOPNOTSUPP;
}
if (drmGetCap(fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) < 0 || !cap) {
fprintf(stderr, "drm device '%s' does not support atomic KMS\n",
node);
close(fd);
return -EOPNOTSUPP;
}
*out = fd;
return 0;
}
/
get_property_value()是一个新函数。给定一个设备,一个对象的属性和一个名称,
搜索属性“name”的值。如果找不到,返回-1。
通过
/
static int64_t get_property_value(int fd, drmModeObjectPropertiesPtr props,const char *name)
{
drmModePropertyPtr prop; //单设备属性结构体指针
uint64_t value;
bool found; //是否找到的设备的标记变量
int j;
found = false;
for (j = 0; j < props->count_props && !found; j++) { //只要找到一个设备则循环结束
prop = drmModeGetProperty(fd, props->props[j]);
if (!strcmp(prop->name, name)) {
value = props->prop_values[j];
found = true;
}
drmModeFreeProperty(prop);
}
if (!found)
return -1;
return value;
}
/
get_drm_object_properties()是一个新的helpfer函数,它检索特定CRTC、平面
或连接器对象的属性。
/
static void modeset_get_object_properties(int fd, struct drm_object *obj,
uint32_t type)
{
const char *type_str;
unsigned int i;
obj->props = drmModeObjectGetProperties(fd, obj->id, type);
if (!obj->props) {//查找设备的是某种设备(如下面的三种)
switch(type) {
case DRM_MODE_OBJECT_CONNECTOR:
type_str = "connector";
break;
case DRM_MODE_OBJECT_PLANE:
type_str = "plane";
break;
case DRM_MODE_OBJECT_CRTC:
type_str = "CRTC";
break;
default:
type_str = "unknown type";
break;
}
fprintf(stderr, "cannot get %s %d properties: %s\n",type_str, obj->id, strerror(errno));
return;
}
obj->props_info = calloc(obj->props->count_props, sizeof(obj->props_info));
for (i = 0; i < obj->props->count_props; i++)
obj->props_info[i] = drmModeGetProperty(fd, obj->props->props[i]);
}
/
set_drm_object_property()是个新函数。它将属性值设置为CRTC、plane或connector对象。
/
static int set_drm_object_property(drmModeAtomicReq *req, struct drm_object *obj,
const char *name, uint64_t value)
{
int i;
uint32_t prop_id = 0;
for (i = 0; i < obj->props->count_props; i++) {
if (!strcmp(obj->props_info[i]->name, name)) {
prop_id = obj->props_info[i]->prop_id;
break;
}
}
if (prop_id == 0) {
fprintf(stderr, "no object property: %s\n", name);
return -EINVAL;
}
return drmModeAtomicAddProperty(req, obj->id, prop_id, value);
}
/
modeset_find_crtc()发生了一些变化。现在,我们还必须保存CRTC索引,而不仅仅是它的id。
/
static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
struct modeset_output *out)
{
drmModeEncoder *enc;
unsigned int i, j;
uint32_t crtc;
struct modeset_output *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 = output_list; iter; iter = iter->next) {
if (iter->crtc.id == crtc) {
crtc = 0;
break;
}
}
if (crtc > 0) {
drmModeFreeEncoder(enc);
out->crtc.id = 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) {
/ 检查此CRTC是否与编码器一起工作 /
if (!(enc->possible_crtcs & (1 << j)))
continue;
/ 检查是否有其他输出已经使用这个CRTC /
crtc = res->crtcs[j];
for (iter = output_list; iter; iter = iter->next) {
if (iter->crtc.id == crtc) {
crtc = 0;
break;
}
}
/
我们已经找到一个CRTC,所以保存它并返回。注意,我们也必须保存它的索引。
在搜索合适的plane时,将使用CRTC索引(而不是它的ID)。
/
if (crtc > 0) {
fprintf(stdout, "crtc %u found for encoder %u, will need full modeset\n",
crtc, conn->encoders[i]);;
drmModeFreeEncoder(enc);
out->crtc.id = crtc;
out->crtc_index = j;
return 0;
}
}
drmModeFreeEncoder(enc);
}
fprintf(stderr, "cannot find suitable crtc for connector %u\n",
conn->connector_id);
return -ENOENT;
}
/
modeset_find_plane()是一个新函数。给定一个connector+CRTC的组合,它为它寻找一个主平面。
/
static int modeset_find_plane(int fd, struct modeset_output *out)
{
drmModePlaneResPtr plane_res;
bool found_primary = false;
int i, ret = -EINVAL;
plane_res = drmModeGetPlaneResources(fd);
if (!plane_res) {
fprintf(stderr, "drmModeGetPlaneResources failed: %s\n",
strerror(errno));
return -ENOENT;
}
/ 迭代某一设备的所有平面 /
for (i = 0; (i < plane_res->count_planes) && !found_primary; i++) {
int plane_id = plane_res->planes[i];
drmModePlanePtr plane = drmModeGetPlane(fd, plane_id);
if (!plane) {
fprintf(stderr, "drmModeGetPlane(%u) failed: %s\n", plane_id,
strerror(errno));
continue;
}
/ 检查飞机是否可以被我们的CRTC使用 /
if (plane->possible_crtcs & (1 << out->crtc_index)) {
drmModeObjectPropertiesPtr props =
drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
/
获取“type”属性来检查这是否是一个主平面。Type属性是特殊的,因为它的enum值在UAPI头文件中定义。
对于没有在UAPI头文件中定义的属性,我们必须给内核属性名,它将返回相应的enum值。我们也可以对“type”属性
这样做,但这会使这个简单的示例变得更复杂。不赞成在UAPI头文件中为内核属性定义枚举值的原因是字符串名称更
容易(用户空间和内核)使其惟一,并在驱动程序和内核版本之间保持一致。但是为了不破坏用户空间,在UAPI头中也
保留了一些属性。
/
if (get_property_value(fd, props, "type") == DRM_PLANE_TYPE_PRIMARY) {
found_primary = true;
out->plane.id = plane_id;
ret = 0;
}
drmModeFreeObjectProperties(props);
}
drmModeFreePlane(plane);
}
drmModeFreePlaneResources(plane_res);
if (found_primary)
fprintf(stdout, "found primary plane, id: %d\n", out->plane.id);
else
fprintf(stdout, "couldn't find a primary plane\n");
return ret;
}
/
modeset_drm_object_fini()是一个新的helper函数,它破坏了crtc、连接器和平面
/
static void modeset_drm_object_fini(struct drm_object *obj)
{
for (int i = 0; i < obj->props->count_props; i++)
drmModeFreeProperty(obj->props_info[i]);
free(obj->props_info);
drmModeFreeObjectProperties(obj->props);
}
/
modeset_setup_objects()是一个新函数。它帮助我们从设备中检索连接器、CRTC和平面对象属性。
这些属性将在原子模式设置提交期间帮助我们,因此我们将它们保存在结构modeset_output对象中。
/
static int modeset_setup_objects(int fd, struct modeset_output *out)
{
struct drm_object *connector = &out->connector;
struct drm_object *crtc = &out->crtc;
struct drm_object *plane = &out->plane;
/ 从设备中检索连接器属性 /
modeset_get_object_properties(fd, connector, DRM_MODE_OBJECT_CONNECTOR);
if (!connector->props)
goto out_conn;
/ 从设备中检索CRTC属性 /
modeset_get_object_properties(fd, crtc, DRM_MODE_OBJECT_CRTC);
if (!crtc->props)
goto out_crtc;
/ 从设备中检索平面属性 /
modeset_get_object_properties(fd, plane, DRM_MODE_OBJECT_PLANE);
if (!plane->props)
goto out_plane;
return 0;
out_plane:
modeset_drm_object_fini(crtc);
out_crtc:
modeset_drm_object_fini(connector);
out_conn:
return -ENOMEM;
}
/
modeset_destroy_objects()是一个新函数。它破坏了我们在modeset_setup_objects()中分配的内容
/
static void modeset_destroy_objects(int fd, struct modeset_output *out)
{
modeset_drm_object_fini(&out->connector);
modeset_drm_object_fini(&out->crtc);
modeset_drm_object_fini(&out->plane);
}
/
create fb()保持不变。
/
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;
uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
/ 创建哑缓冲区 /
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对象 /
handles[0] = buf->handle;
pitches[0] = buf->stride;
ret = drmModeAddFB2(fd, buf->width, buf->height, DRM_FORMAT_XRGB8888,
handles, pitches, offsets, &buf->fb, 0);
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;
}
/
destroy 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);
}
/
modeset_setup_framebuffers()为某个输出的前、后缓冲区创建帧缓冲区。此外,它还将连接器模式复制到这些缓冲区。
/
static int modeset_setup_framebuffers(int fd, drmModeConnector *conn,
struct modeset_output *out)
{
int i, ret;
/ 设置前后帧缓冲区 /
for (i = 0; i < 2; i++) {
/ 复制模式信息到缓冲区 /
out->bufs[i].width = conn->modes[0].hdisplay;
out->bufs[i].height = conn->modes[0].vdisplay;
/ 为缓冲区创建一个framebuffer/
ret = modeset_create_fb(fd, &out->bufs[i]);
if (ret) {
/ 第二次创建framebuffer失败,所以我们必须在返回之前销毁第一次 /
if (i == 1)
modeset_destroy_fb(fd, &out->bufs[0]);
return ret;
}
}
return 0;
}
/
modeset_output_destroy()是新的。它破坏对象(连接器、crtc和平面)、前缓冲区和后缓冲区、mode blob属性,然后破坏输出本身。
/
static void modeset_output_destroy(int fd, struct modeset_output *out)
{
/* destroy connector, crtc and plane objects */
modeset_destroy_objects(fd, out);
/* destroy front/back framebuffers */
modeset_destroy_fb(fd, &out->bufs[0]);
modeset_destroy_fb(fd, &out->bufs[1]);
/* destroy mode blob property */
drmModeDestroyPropertyBlob(fd, out->mode_blob_id);
free(out);
}
/
通过一定的connector+CRTC组合,我们为它寻找一个合适的主平面。之后,我们从设备中检索连接器、CRTC和平面
对象属性。这些对象在原子模式集设置期间使用(参见modeset_atomic_prepare_commit()),也在页面翻转期间使用
(参见modeset_draw_out()和modeset_atomic_commit())。
除此之外,我们还必须创建一个接收输出模式的blob属性。当我们执行原子提交时,驱动程序需要一个名为“MODE_ID”
的CRTC属性,它指向一个blob的id。这通常发生在不是简单类型的属性上。在本例中,out->模式是一个结构体。但是,我们可
以有另一个属性,它需要保存数组的blob的id。
/
static struct modeset_output *modeset_output_create(int fd, drmModeRes *res,
drmModeConnector *conn)
{
int ret;
struct modeset_output *out;
/ 创建一个输出结构 /
out = malloc(sizeof(*out));
memset(out, 0, sizeof(*out));
out->connector.id = conn->connector_id;
/ 检查是否有显示器连接 /
if (conn->connection != DRM_MODE_CONNECTED) {
fprintf(stderr, "ignoring unused connector %u\n",
conn->connector_id);
goto out_error;
}
/ 检查是否至少有一个有效的模式 /
if (conn->count_modes == 0) {
fprintf(stderr, "no valid mode for connector %u\n",
conn->connector_id);
goto out_error;
}
/ 复制模式信息到我们的输出结构中 /
memcpy(&out->mode, &conn->modes[0], sizeof(out->mode));
/ 创建blob属性使用out->模式,并将其id保存在输出 /
if (drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode),
&out->mode_blob_id) != 0) {
fprintf(stderr, "couldn't create a blob property\n");
goto out_error;
}
fprintf(stderr, "mode for connector %u is %ux%u\n",
conn->connector_id, out->bufs[0].width, out->bufs[0].height);
/ 为这个连接器找到一个crtc /
ret = modeset_find_crtc(fd, res, conn, out);
if (ret) {
fprintf(stderr, "no valid crtc for connector %u\n",
conn->connector_id);
goto out_blob;
}
/ 通过一个连接器和crtc,找到一个主平面(plane) /
ret = modeset_find_plane(fd, out);
if (ret) {
fprintf(stderr, "no valid plane for crtc %u\n", out->crtc.id);
goto out_blob;
}
/ 收集连接器,CRTC和平面(plane)的属性 /
ret = modeset_setup_objects(fd, out);
if (ret) {
fprintf(stderr, "cannot get plane properties\n");
goto out_blob;
}
/ 为这个CRTC设置前置/后置帧缓冲区 /
ret = modeset_setup_framebuffers(fd, conn, out);
if (ret) {
fprintf(stderr, "cannot create framebuffers for connector %u\n",
conn->connector_id);
goto out_obj;
}
return out;
out_obj:
modeset_destroy_objects(fd, out);
out_blob:
drmModeDestroyPropertyBlob(fd, out->mode_blob_id);
out_error:
free(out);
return NULL;
}
/
modeset_prepare()有一些变化。现在我们使用新函数
modeset_output_create()来分配内存并设置输出。
/
static int modeset_prepare(int fd)
{
drmModeRes *res;
drmModeConnector *conn;
unsigned int i;
struct modeset_output *out;
/ 检索资源 /
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;
}
/ 创建一个输出结构和自由连接器数据 /
out = modeset_output_create(fd, res, conn);
drmModeFreeConnector(conn);
if (!out)
continue;
/ 链接输出到全局列表 /
out->next = output_list;
output_list = out;
}
if (!output_list) {
fprintf(stderr, "couldn't create any outputs\n");
return -1;
}
/ 再次释放资源 /
drmModeFreeResources(res);
return 0;
}
/
modeset_atomic_prepare_commit()是新的。这里,我们设置了想要在原子提交中更改的属性
(连接器、CRTC和平面对象的属性)的值。这些更改临时存储在drmModeAtomicReq *req中,直到提交实际发生。
/
static int modeset_atomic_prepare_commit(int fd, struct modeset_output *out,
drmModeAtomicReq *req)
{
struct drm_object *plane = &out->plane;
struct modeset_buf *buf = &out->bufs[out->front_buf ^ 1];
/ 设置连接器使用的CRTC id /
if (set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id) < 0)
return -1;
/ 设置CRTC的模式id;这个属性接收一个blob属性的id,该属性包含实际包含模式信息的结构 /
if (set_drm_object_property(req, &out->crtc, "MODE_ID", out->mode_blob_id) < 0)
return -1;
/ 设置CRTC对象为active /
if (set_drm_object_property(req, &out->crtc, "ACTIVE", 1) < 0)
return -1;
/ 设置与CRTC和framebuffer相关的平面属性 /
if (set_drm_object_property(req, plane, "FB_ID", buf->fb) < 0)
return -1;
if (set_drm_object_property(req, plane, "CRTC_ID", out->crtc.id) < 0)
return -1;
if (set_drm_object_property(req, plane, "SRC_X", 0) < 0)
return -1;
if (set_drm_object_property(req, plane, "SRC_Y", 0) < 0)
return -1;
if (set_drm_object_property(req, plane, "SRC_W", buf->width << 16) < 0)
return -1;
if (set_drm_object_property(req, plane, "SRC_H", buf->height << 16) < 0)
return -1;
if (set_drm_object_property(req, plane, "CRTC_X", 0) < 0)
return -1;
if (set_drm_object_property(req, plane, "CRTC_Y", 0) < 0)
return -1;
if (set_drm_object_property(req, plane, "CRTC_W", buf->width) < 0)
return -1;
if (set_drm_object_property(req, plane, "CRTC_H", buf->height) < 0)
return -1;
return 0;
}
/
一个简短的辅助函数来计算变化的颜色值。没必要去理解它。
/
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;
}
/
在请求翻页之前在后台framebuffer上绘制。
/
static void modeset_paint_framebuffer(struct modeset_output *out)
{
struct modeset_buf *buf;
unsigned int j, k, off;
/* draw on back framebuffer */
out->r = next_color(&out->r_up, out->r, 5);
out->g = next_color(&out->g_up, out->g, 5);
out->b = next_color(&out->b_up, out->b, 5);
buf = &out->bufs[out->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] =
(out->r << 16) | (out->g << 8) | out->b;
}
}
}
/
modeset_draw_out()用绘图准备framebuffer,然后请求驱动程序执行原子提交。这将导致
页面翻转,并显示framebuffer的内容。在这个简单的示例中,我们只使用了主平面,但是我们也可以
在同一个原子提交中更新其他平面。
就像在modeset_perform_modeset()中一样,我们首先使用modeset_atomic_prepare_commit()
设置一切,然后实际执行原子提交。但有一些重要的区别:
1. 在这里,我们只是想执行一个更改特定输出状态的提交,在modeset_perform_modeset()中,
我们执行了一个原子提交,假定它一次性设置所有输出。因此,在执行原子提交之前,不需要准备每个输出。
但是,让我们假设您准备好了每个输出,然后执行提交。它应该为所有这些分页安排一次翻页,但是
调用modeset_draw_out()是因为特定输出的翻页已经完成。其他的可能还没有准备好翻页(例如在扫描的中间),
所以这些翻页将失败。
2. 在这里,我们已经绘制了帧缓冲区,并且不再使用标志DRM_MODE_ALLOW_MODESET,因为modeset已
经发生了。我们可以继续使用这个标志,因为如果modeset_perform_modeset()是正确的,并且内核中没有bug,
那么它也没有什么区别。这个标志只允许(它不强制)驱动程序执行一个modeset,但是我们已经
在modeset_perform_modeset()中执行了它,现在我们只希望发生翻页。如果我们仍然需要执行modesset,这意
味着我们在某个地方存在漏洞,失败可能比故障好(一个modesset可能会导致不必要的延迟,也会导致屏幕空白)。
/
static void modeset_draw_out(int fd, struct modeset_output *out)
{
drmModeAtomicReq *req;
int ret, flags;
/ draw on framebuffer的输出 /
modeset_paint_framebuffer(out);
/ 为原子提交准备输出 /
req = drmModeAtomicAlloc();
ret = modeset_atomic_prepare_commit(fd, out, req);
if (ret < 0) {
fprintf(stderr, "prepare atomic commit failed, %d\n", errno);
return;
}
/
我们刚刚在framebuffer上绘图,准备好提交,现在是时候执行页面翻转来显示其内容了。
DRM_MODE_PAGE_FLIP_EVENT表示当页面翻页发生时,我们希望在DRM-fd中接收页面翻页事件。
这个标志也在非原子示例中使用,所以您可能对它很熟悉。
DRM_MODE_ATOMIC_NONBLOCK使翻页无阻塞。例如,我们不想在等待提交时被阻塞,因为我们
可以利用这段时间准备一个新的framebuffer。我们之所以这样做,是因为有一些机制可以知道提交何
时完成(比如上面解释的页面翻转事件)。
/
flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
ret = drmModeAtomicCommit(fd, req, flags, NULL);
drmModeAtomicFree(req);
if (ret < 0) {
fprintf(stderr, "atomic commit failed, %d\n", errno);
return;
}
out->front_buf ^= 1;
out->pflip_pending = true;
}
/
modeset_page_flip_event()的变化。现在我们使用了page_flip_handler2,我们还接收了负责此
事件的CRTC。当使用原子API时,我们一次提交多个CRTC,因此我们需要输出导致事件的信息,以便为它安排
新的页面翻页。
/
static void modeset_page_flip_event(int fd, unsigned int frame,
unsigned int sec, unsigned int usec,
unsigned int crtc_id, void *data)
{
struct modeset_output *out, *iter;
/ 查找负责此事件的输出 /
out = NULL;
for (iter = output_list; iter; iter = iter->next) {
if (iter->crtc.id == crtc_id) {
out = iter;
break;
}
}
if (out == NULL)
return;
out->pflip_pending = false;
if (!out->cleanup)
modeset_draw_out(fd, out);
}
/
modeset_perform_modeset()是新的。首先,我们定义必须更改哪些属性以及它们将接收到的值。
为了检查modeset是否会像预期的那样工作,我们使用标志DRM_MODE_ATOMIC_TEST_ONLY执行原子提交。
有了这个标志,DRM驱动程序会测试原子提交是否可以工作,但它不会将其提交给硬件。之后,在不
带TEST_ONLY标志的情况下执行同样的原子提交,而且不仅仅是在我们绘制输出的framebuffer之前。
这对于避免显示不需要的内容是必要的。
注意:我们不能在没有附加frambeuffer的情况下执行原子提交(即使有DRM_MODE_ATOMIC_TEST_ONLY)。
它只会失败。
/
static int modeset_perform_modeset(int fd)
{
int ret, flags;
struct modeset_output *iter;
drmModeAtomicReq *req;
/ 在所有输出上准备模式集 /
req = drmModeAtomicAlloc();
for (iter = output_list; iter; iter = iter->next) {
ret = modeset_atomic_prepare_commit(fd, iter, req);
if (ret < 0)
break;
}
if (ret < 0) {
fprintf(stderr, "prepare atomic commit failed, %d\n", errno);
return ret;
}
/ 执行只测试的原子提交 /
flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET;
ret = drmModeAtomicCommit(fd, req, flags, NULL);
if (ret < 0) {
fprintf(stderr, "test-only atomic commit failed, %d\n", errno);
drmModeAtomicFree(req);
return ret;
}
/ 利用所有输出的back framebuffer /
for (iter = output_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;
modeset_paint_framebuffer(iter);
}
/ 初始模式设置在所有输出 /
flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT;
ret = drmModeAtomicCommit(fd, req, flags, NULL);
if (ret < 0)
fprintf(stderr, "modeset atomic commit failed, %d\n", errno);
drmModeAtomicFree(req);
return ret;
}
/
modeset_draw()的变化。如果我们到了这里,模式集已经发生了。当完成某个输出的页面翻页时,
将触发一个事件,我们将能够处理它。
这里我们定义了应该处理这些事件的函数,它是modeset_page_flip_event()。这个函数调
用modeset_draw_out(),它负责准备一个新的framebuffer并为我们执行另一个原子提交。
然后,我们有一个5秒的循环,一直等待页面翻页完成时触发的事件。drmHandleEvent()负责
从fd读取事件,并为每个事件调用modeset_page_flip_event()。
/
static void modeset_draw(int fd)
{
int ret;
fd_set fds;
time_t start, cur;
struct timeval v;
drmEventContext ev;
/ 初始化变量/
srand(time(&start));
FD_ZERO(&fds);
memset(&v, 0, sizeof(v));
memset(&ev, 0, sizeof(ev));
/
3是允许我们使用page_flip_handler2的第一个版本,它与page_flip_handler类似,
但添加了将crtc_id作为参数传递给处理页面翻转事件的函数(在我们的例子中是modeset_page_flip_event())。
这很好,因为我们可以找出翻页发生了什么输出。
page_flip_handler2的使用是我们需要验证对DRM_CAP_CRTC_IN_VBLANK_EVENT支持的原因。
/
ev.version = 3;
ev.page_flip_handler2 = modeset_page_flip_event;
/ 执行modesset使用原子提交 /
modeset_perform_modeset(fd);
/ 等待5s的VBLANK或输入事件 /
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)) {
/ 读取fd查找事件并通过调用modeset_page_flip_event()处理每个事件 /
drmHandleEvent(fd, &ev);
}
}
}
/
modesset cleanup()保持不变
/
static void modeset_cleanup(int fd)
{
struct modeset_output *iter;
drmEventContext ev;
int ret;
/ 初始化变量 /
memset(&ev, 0, sizeof(ev));
ev.version = 3;
ev.page_flip_handler2 = modeset_page_flip_event;
while (output_list) {
/ 从list *获取第一个输出 /
iter = output_list;
/ 如果一个页面翻页是挂起的,等待它完成 /
iter->cleanup = true;
fprintf(stderr, "wait for pending page-flip to complete...\n");
while (iter->pflip_pending) {
ret = drmHandleEvent(fd, &ev);
if (ret)
break;
}
/ 移动列表头到下一个输出 /
output_list = iter->next;
/ 销毁当前输出 /
modeset_output_destroy(fd, iter);
}
}
/
main()也在改变。我们没有调用drmModeSetCrtc()来执行KMS设置,
而是使用原子API和函数modeset_perform_modeset()来设置它,该函数由modeset_draw()调用。
/
int main(int argc, char **argv)
{
int ret, fd;
const char *card;
/ 检查打开哪个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;
/ 画一些颜色,持续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;
}
/
这是一个非常简单的示例,展示了如何使用KMS原子API,以及它与遗留KMS的区别。
大多数现代驱动程序都在使用原子API,所以有这个示例很重要。
就像vsync'ed double-buffering一样,原子API并不能解决可能发生的所有问题,
你必须为你的用例找出最佳的实现。
如果你想看看使用KMS atomic API的更复杂的例子,我可以向你推荐:
——kms-quads: https://gitlab.freedesktop.org/daniels/kms-quads/
一个更复杂(但也解释得很好)的示例可以用于
学习如何使用原子API构建合成器。同时支持GL和软件渲染。
-韦斯顿:https://gitlab.freedesktop.org/wayland/weston
韦兰排字机的参考实现。这是一个非常复杂的DRM渲染器,很难完全理解,因为它使用了更多
复杂的技术,如DRM飞机。
欢迎任何反馈。您可以在自己的文档或项目中自由地使用这些代码。
-由http://github.com/dvdhrm/docs主持
/