@@@表示程序中有具体的函数
  1️⃣~8️⃣表示不可缺少的步骤(两个6️⃣无7)
  目录可用来整理整体思路

简化描述

[TOC]

思维导图

一、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使用硬件加速,实际上用libgbmlibEGL创建缓冲区是相当容易的。但这超出了本文的讨论范围。
         所以我们要做的是从驱动程序请求一个新的哑缓冲区(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);

思维导图