EGL、GBM学习

参考代码链接

EGL Off-Screen rendering using GBM:https://blog.csdn.net/eydwyz/article/details/107046470

kmscube:https://gitlab.freedesktop.org/mesa/kmscube

https://github.com/eyelash/tutorials/blob/master/drm-gbm.c

显示服务器实现(一):https://zhuanlan.zhihu.com/p/268527301

https://github.com/yuq/minix/blob/master/gbm-surface/main.c

https://github.com/yuq/minix/blob/master/drm-display/main.c

MESA源码分析:GBM、EGL:https://crab2313.github.io/post/mesa-gbm/、https://crab2313.github.io/post/mesa-egl/

GBM概念

Mesa GBM ( Generic Buffer Manager) 基本上提供了 EGL native window类型(就像 Wayland 和 X11),因此可以获得真实的 EGL 表面并创建渲染目标缓冲区。然后,GL 可用于渲染这些缓冲区,这些缓冲区将通过 KMS/DRM API 排队翻页显示在显示器上。

用户应用程序直接对内存进行管理,通过 EGL 可以获取真实的 EGL 表面并创建渲染目标缓冲区

gbm(通用缓冲区管理),它提供了一种为 Mesa 绑定的图形渲染分配缓冲区的机制。GBM 旨在被当做一个本地平台为了工作在 DRM 上的 EGL 或者 openwfd。它创建的句柄可用于初始化 EGL 和创建渲染目标缓冲区。

Mesa GBM(Generic Buffer Management)是一个开源图形缓冲区管理库,用于管理图形内存缓冲区。它是Mesa 3D图形库的一部分。GBM主要用于Linux平台,为Direct Rendering Manager (DRM)内核子系统提供了一个用户空间API。它提供了一种统一的接口,用于在Linux系统中管理图形缓冲区和设备之间的交互。

GBM的主要功能包括:

  1. 分配图形内存缓冲区:GBM提供了一种简单的方法来分配图形内存缓冲区,这些缓冲区可以用于存储图像数据、纹理等。它支持多种像素格式和布局,以满足不同的图形需求。GBM提供了一套函数和数据结构,用于创建、管理和销毁图形缓冲区。它支持不同的图像格式和属性,并且可以与不同的图形设备(如显卡)进行交互。
  2. 管理缓冲区对象:GBM提供了缓冲区对象(buffer objects)的概念,用于表示分配的图形内存缓冲区。缓冲区对象可以与DRM内核子系统进行交互,以便进行模式设置、页面翻转等操作。
  3. 设备抽象:GBM提供了一种抽象接口,用于与底层图形设备(如DRM设备)进行交互。它隐藏了底层设备的细节,并提供了一致的接口,使上层组件(如EGL和显卡驱动程序)能够以统一的方式使用不同的设备。
  4. 内存映射:GBM允许对图形缓冲区进行内存映射,以便在需要时可以直接访问缓冲区的内容。这对于一些图形处理任务(如渲染、纹理操作)非常有用,可以提高性能和效率。
  5. 上下文管理:GBM提供了上下文管理的功能,可以创建和管理与图形缓冲区相关的上下文。这些上下文用于进行渲染和图形操作,并可以在不同的线程或进程之间共享。
  6. 提供与EGL的集成:EGL是一个用于管理OpenGL和OpenGL ES上下文的平台无关API。GBM提供了与EGL的集成,使得开发者可以在使用GBM管理图形内存的同时,使用EGL来创建和管理OpenGL上下文。
  7. 提供与Wayland和X11的集成:GBM可以与Wayland和X11这样的显示服务器进行集成,以便在这些平台上进行图形渲染。这使得GBM成为了Linux平台上的一个通用图形内存管理解决方案。

Mesa GBM在Linux图形堆栈中扮演着重要的角色,它作为一个中间层,连接了底层的图形设备和上层的图形组件。它为应用程序、窗口系统和驱动程序提供了一种统一的接口,使它们能够协同工作,实现图形渲染、显示和交互等功能。

GBM相关函数解析

​ 我们先从原理上对GBM进行推导。GBM的本质:硬件无关的显存申请与管理。为什么需要这么一个库:DRM自带的DUMB缓冲区不能申请GPU显存,不能使用3D加速。因此,GBM的基本操作就是显存的申请与释放。但是这个管理必有一个上下文,很明显不可能是全局的,因为哪怕是作为用户的我们,也肯定希望申请出的GPU显存是与某个GPU绑定的。这个上下文由struct gbm_device进行抽象,且必定是与DRM设备绑定的。

gbm_create_device

struct gbm_device *gbm_create_device(int fd);

这里可以看到一段示例代码,gbm_create_device的参数fd实际上就是打开一个DRM设备后得到的文件描述符。注意实际上文件描述符并不一定必须是DRM文件描述符,这是因为GBM是支持多种后端的,gbm_create_device函数可以在/usr/lib/gbm/文件夹下找到形如<driver_name>_gbm.so的后端,然后装载使用(这个主要是给NVIDIA使用的)。由于我们是分析MESA的代码,这里假定只有一个dri后端。

经过层层dispatch后,最终调用的是dri_device_create函数。这里我们先看一下struct gbm_device的实现:

struct gbm_device {
   /* Hack to make a gbm_device detectable by its first element. */
   struct gbm_device *(*dummy)(int);
   struct gbm_device_v0 v0;
};

这里提一嘴,第一个dummy元素必定会被设置成gbm_create_device函数的地址,这么做的意义就是后面EGL GBM扩展时,使用eglGetDisplay并传入gbm_device指针时,函数可以直接通过第一个字段是否为gbm_create_device的地址来判断传入的是否是gbm_device,从而决定是否使用GBM后端。而struct gbm_device_v0则是一个常见的trick,来保证ABI不会改变。除此之外,struct gbm_device_v0本质上就保存了后端信息,文件描述符,以及一大堆函数指针,用于GBM库其他API的dispatch。

回到dri_create_device,函数除了设置上面提到的函数指针之外,唯一做的工作就是如下:

 force_sw = env_var_as_boolean("GBM_ALWAYS_SOFTWARE", false);
   if (!force_sw) {
      ret = dri_screen_create(dri);
      if (ret)
         ret = dri_screen_create_sw(dri);
   } else {
      ret = dri_screen_create_sw(dri);
   }
//根据force_sw变量的值选择创建硬件渲染还是软件渲染的DRI屏幕
//这种做法可以在支持硬件加速的情况下优先使用硬件渲染,但如果硬件渲染不可用或失败,则回退到软件渲染以确保程序的正常运行。

很明显,dri后端需要对struct gbm_device进行扩展,得到struct gbm_dri_device。与EGL中实现一样,该结构体对DRI驱动装载后得到的各类扩展进行了存储。经过分析,可以对dri_screen_create进行的操作总结如下:

  • 从DRM文件描述符中得到DRM驱动的名称
  • 根据驱动名称装载对应的用户态DRI驱动
  • 绑定loader,core等扩展,得到函数指针
  • 调用DRI_DRI2上的createNewScreen2方法,得到__DRIScreen并保存

gbm_bo_create

GBM_EXPORT struct gbm_bo *
gbm_bo_create_with_modifiers2(struct gbm_device *gbm,
                              uint32_t width, uint32_t height,
                              uint32_t format,
                              const uint64_t *modifiers,
                              const unsigned int count,
                              uint32_t flags);

该函数有多个变体,如gbm_bo_create_with_modifiers2,本质上这几个变体最后都dispatch到gbm_dri_bo_create函数。先来逐步理清这个函数的意义及用法:

  • BO是什么?Buffer Object的缩写,在内核驱动中,这样一个缓冲区被称作Buffer Object,这是因为围绕着这个缓冲区对象,有相当多的method。其次,Buffer Object的位置实际上并不固定,可以在GPU独立显存,内存中移动,所以需要这么一个object的描述符来引用它。gbm_bo_create本质上就是GBM的核心操作,请求显存的分配。

  • 为什么这个函数这么复杂?本质上还是硬件本身就复杂。现代GPU对显存的管理相当的精细,有GPU端的MMU负责管理GPU独立显存,系统内存,同时也管理显存类型,如普通显存,tile显存等等。同时,每一段显存可以执行的操作也不同,比如用于渲染的显存,用于scanout的显存。除此之外,还存在显存共享的需求,虽然现在已经有DMA-BUF接口,但是一个新的问题就是不同GPU对于显存格式的定义是不同的,需要更加精细的描述显存的格式。这也就是modifier出现的意义。

  • 所以函数的这几个参数本质上就是:format =>格式,modifier => 显存modifier,flags => 显存用途。

    static struct gbm_bo *
    gbm_dri_bo_create(struct gbm_device *gbm,
                      uint32_t width, uint32_t height,
                      uint32_t format, uint32_t usage,
                      const uint64_t *modifiers,
                      const unsigned int count)
    {
       struct gbm_dri_device *dri = gbm_dri_device(gbm);
       struct gbm_dri_bo *bo;
       int dri_format;
       unsigned dri_use = 0;
    
       format = gbm_core.v0.format_canonicalize(format);
    
       if (usage & GBM_BO_USE_WRITE || dri->image == NULL)
          return create_dumb(gbm, width, height, format, usage);
    
       bo = calloc(1, sizeof *bo);
       if (bo == NULL)
          return NULL;
    
       bo->base.gbm = gbm;
       bo->base.v0.width = width;
       bo->base.v0.height = height;
       bo->base.v0.format = format;
    
       dri_format = gbm_format_to_dri_format(format);
       if (dri_format == 0) {
          errno = EINVAL;
          goto failed;
       }
    
       if (usage & GBM_BO_USE_SCANOUT)
          dri_use |= __DRI_IMAGE_USE_SCANOUT;
       if (usage & GBM_BO_USE_CURSOR)
          dri_use |= __DRI_IMAGE_USE_CURSOR;
       if (usage & GBM_BO_USE_LINEAR)
          dri_use |= __DRI_IMAGE_USE_LINEAR;
       if (usage & GBM_BO_USE_PROTECTED)
          dri_use |= __DRI_IMAGE_USE_PROTECTED;
       if (usage & GBM_BO_USE_FRONT_RENDERING)
          dri_use |= __DRI_IMAGE_USE_FRONT_RENDERING;
    
       /* Gallium drivers requires shared in order to get the handle/stride */
       dri_use |= __DRI_IMAGE_USE_SHARE;
    
       if (modifiers && (dri->image->base.version < 14 ||
           !dri->image->createImageWithModifiers)) {
          errno = ENOSYS;
          goto failed;
       }
    
       bo->image = loader_dri_create_image(dri->screen, dri->image, width, height,
                                           dri_format, dri_use, modifiers, count,
                                           bo);
       if (bo->image == NULL)
          goto failed;
    
       if (modifiers)
          assert(gbm_dri_bo_get_modifier(&bo->base) != DRM_FORMAT_MOD_INVALID);
    
       dri->image->queryImage(bo->image, __DRI_IMAGE_ATTRIB_HANDLE,
                              &bo->base.v0.handle.s32);
       dri->image->queryImage(bo->image, __DRI_IMAGE_ATTRIB_STRIDE,
                              (int *) &bo->base.v0.stride);
    
       return &bo->base;
    
    failed:
       free(bo);
       return NULL;
    }
    

在明白用法之后,我们来看gbm_dri_bo_create的实现。首先函数开头可以看到一个实现上的细节,如果我们向GBM请求一个GBM_BO_USE_WRITE用途的BO,但是后端没有DRI_IMAGE扩展时,则GBM的DRI后端会自动fallback回DUMB Buffer:

   if (usage & GBM_BO_USE_WRITE || dri->image == NULL)
      return create_dumb(gbm, width, height, format, usage);

后面进行的一些操作简单就是将一些GBM认识的flags与格式转换成DRI认识的格式,这基本是个一一映射的状态。随后调用loader_dri_create_image

  bo->image = loader_dri_create_image(dri->screen, dri->image, width, height,
                                       dri_format, dri_use, modifiers, count,
                                       bo);
    //调用loader_dri_create_image函数创建一个图像,并将返回的图像对象赋值给bo->image。

这个函数的本质就是调用DRI_IMAGE扩展上的createImageWithModifiers族函数创建一个__DRIimage并保存在BO对象中。也就是说,对于DRI后端,BO对象实质上就是__DRIimage对象的wrapper,且GBM本身借助DRI_IMAGE扩展实现显存的通用分配,所以GBM就是一个壳子而已。最后函数通过queryImage方法获取该__DRIimage的stride和handle,并缓存起来。

总结一下,GBM的DRI后端对于BO的管理,本质上就是依靠于DRI驱动的DRI_IMAGE扩展。常见的操作比如BO创建,map,释放等都是直接调扩展中提供的方法实现的。而gbm_dri_bo本身,其实也就是__DRIimage的wrapper。

gbm_surface_create

要理解struct gbm_surface相关的API到底是干什么的,必须要理解compositor的工作原理。首先我们要明白为什么要直接申请显存,直接用opengl等API不行么?事实上我们直接使用GBM进行显存管理的场景就是实现compositor,或者offscreen渲染。这个使用场景想达成的目标非常简单,即创建一个opengl上下文,使得我们可以直接使用OpenGL往一个framebuffer中渲染,然后将这个framebuffer作为scanout buffer输出到屏幕上。

在wayland协议早期,这类操作是直接使用GBM加上EGL的surfaceless扩展完成的。但是缺点很明显,开发者必须手动申请BO然后导入EGLImage,然后将其提交给surfaceless扩展。后面,MESA开发者提出了一个新的扩展:EGL_KHR_platform_gbm,算是解决了这个问题。本质上,这个扩展允许你直接将DRM设备当作EGLDisplay,并将一个申请好的gbm_surface当作EGLSurface,使得我们可以直接使用eglSwapBuffer等API完成我们想要的工作。这里,gbm_surface本质上就是个stub,记录了这个EGLSurface的基本状态。

struct gbm_dri_surface {
   struct gbm_surface base;

   void *dri_private;
};

struct gbm_surface_v0 {
   uint32_t width;
   uint32_t height;
   uint32_t format;
   uint32_t flags;
   struct {
      uint64_t *modifiers;
      unsigned count;
   };
};
static struct gbm_surface *
gbm_dri_surface_create(struct gbm_device *gbm,
                       uint32_t width, uint32_t height,
		       uint32_t format, uint32_t flags,
                       const uint64_t *modifiers, const unsigned count)
{
   struct gbm_dri_device *dri = gbm_dri_device(gbm);
   struct gbm_dri_surface *surf;
	
	//函数首先检查是否支持修饰器。如果修饰器存在但不受支持(版本低于14或不支持createImageWithModifiers函数),则返回错误。然后,函数对修饰器的数量和值进行检查,并分配内存以保存修饰器列表。
   if (modifiers &&
       (!dri->image || dri->image->base.version < 14 ||
        !dri->image->createImageWithModifiers)) {
      errno = ENOSYS;
      return NULL;
   }

   if (count)
      assert(modifiers);

   if (count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
      fprintf(stderr, "Only invalid modifier specified\n");
      errno = EINVAL;
   }

   surf = calloc(1, sizeof *surf);
   if (surf == NULL) {
      errno = ENOMEM;
      return NULL;
   }
   //接下来,函数填充并返回一个gbm_dri_surface结构体,其中包含了GBM设备、宽度、高度、像素格式、标志和修饰器等信息。
   surf->base.gbm = gbm;
   surf->base.v0.width = width;
   surf->base.v0.height = height;
   surf->base.v0.format = gbm_core.v0.format_canonicalize(format);
   surf->base.v0.flags = flags;
   if (!modifiers) {
      assert(!count);
      return &surf->base;
   }

   surf->base.v0.modifiers = calloc(count, sizeof(*modifiers));
   if (count && !surf->base.v0.modifiers) {
      errno = ENOMEM;
      free(surf);
      return NULL;
   }

   surf->base.v0.count = count;
   memcpy(surf->base.v0.modifiers, modifiers, count * sizeof(*modifiers));

   return &surf->base;
}

gbm_surface_lock_front_buffer

gbm_surface_lock_front_buffer函数用于锁定GBM(Generic Buffer Manager)表面的前端缓冲对象,以便对其进行读取或写入操作。

函数原型如下:

struct gbm_bo *gbm_surface_lock_front_buffer(struct gbm_surface *surface);

参数说明:

  • surface:要锁定前端缓冲对象的GBM表面。

函数返回一个指向struct gbm_bo类型的指针,该指针表示被锁定的前端缓冲对象。如果操作成功,返回的指针是有效的。如果操作失败,返回的指针为NULL。

在调用gbm_surface_lock_front_buffer函数之后,你可以使用返回的前端缓冲对象进行读取或写入操作,例如读取像素数据或将渲染结果保存为图像文件。

需要注意的是,对于每次锁定的前端缓冲对象,通常需要在适当的时候调用gbm_surface_release_buffer函数来释放该对象。

锁定前端缓冲对象是为了直接访问底层的硬件缓冲区,以进行渲染、显示或处理等操作。这在与底层图形设备交互的场景中非常有用,例如在使用GBM和DRM等图形编程中。

往下深入该函数调用栈

在这里插入图片描述

static struct gbm_bo *
lock_front_buffer(struct gbm_surface *_surf)
{
   struct gbm_dri_surface *surf = gbm_dri_surface(_surf);
   struct dri2_egl_surface *dri2_surf = surf->dri_private;
   struct gbm_dri_device *device = gbm_dri_device(_surf->gbm);
   struct gbm_bo *bo;

   if (dri2_surf->current == NULL) {
      _eglError(EGL_BAD_SURFACE, "no front buffer");
      return NULL;
   }

   bo = dri2_surf->current->bo;

   if (device->dri2) {
      dri2_surf->current->locked = true;
      dri2_surf->current = NULL;
   }

   return bo;
}

GBM相关示例

在命令行Linux操作系统中实现一个支持GPU图形应用的显示服务器:

  1. 每个图形应用绘制到一个缓冲(Buffer)
  2. 图形应用将这个缓冲交给显示服务器组装成帧缓冲(Framebuffer)
  3. 显示服务器将帧缓冲显示到显示器上

一个简单的OpenGL应用

这是一个在Linux X11系统下简单的OpenGL应用【1】,效果是在一个X11窗口中绘制一个红色三角形。我们看看OpenGL应用是如何跟显示服务器(XServer)交互的。注意这里忽略了无关参数和代码,下同。

Display *dpy = XOpenDisplay(NULL);
Window window = XCreateWindow(dpy);
EGLDisplay display = eglGetDisplay(dpy);
EGLSurface surface = eglCreateWindowSurface(display, window);
EGLContext context = eglCreateContext(display);
eglMakeCurrent(display, surface, surface, context);
// OpenGL Render
...
eglSwapBuffers(display, surface);

首先用EGL从X11的Display和Window创建Context,然后用OpenGL进行实际绘制,最后用EGL的eglSwapBuffers将绘制好的缓冲交给XServer去显示。

无显示服务器的OpenGL绘制

例二【2】是在没有显示服务器的情况下,用EGL+GBM做OpenGL绘制的例子:

int fd = open("/dev/dri/renderD128");
struct gbm_device *gbm = gbm_create_device(fd);
struct gbm_surface *gs = gbm_surface_create(gbm);
EGLDisplay display = eglGetPlatformDisplayEXT(gbm);
EGLSurface surface = eglCreatePlatformWindowSurfaceEXT(display, gs);
EGLContext context = eglCreateContext(display);
eglMakeCurrent(display, surface, surface, context);
// OpenGL Render
...
eglSwapBuffers(display, surface);

GBM是一个GPU缓冲管理的API,可以直接从GPU的设备文件(/dev/dri/renderD128)创建一个gbm_device,然后再创建一个代表GPU缓冲的gbm_surface。支持EGL_MESA_platform_gbm扩展的EGL接口可以利用gbm_device和gbm_surface来创建EGLDisplay和EGLSurface。接下来就和EGL+X11那个例子一样了。

显示器显示帧缓冲

显示帧缓冲可以参考例三【3】,首先打开GPU设备文件/dev/dri/card0(这个文件同时支持绘图和显示,而/dev/dri/renderD128只有绘图功能)。然后就能调用KMS接口获取当前连着显示器的Connector/Encoder/Crtc,这三个模块的功能:Crtc从帧缓冲读取数据给Encoder,Encoder编码数据给Connector,Connector输出HDMI/DP/VGA接口的信号。最后drmModeFBPtr代表Crtc要读取的帧缓冲。当前drmModeFBPtr所指向的帧缓冲里面应该是命令行界面,我们需要为自己的绘图缓冲创建一个新的drmModeFBPtr。

int fd = open("/dev/dri/card0");
drmModeResPtr res = drmModeGetResources(fd);
drmModeConnectorPtr connector = NULL;
for (int i = 0; i < res->count_connectors; i++) {
    connector = drmModeGetConnector(fd, res->connectors[i]);
    // find a connected connection
    if (connector->connection == DRM_MODE_CONNECTED)
        break;
}
drmModeEncoderPtr encoder = drmModeGetEncoder(fd, connector->encoder_id);
drmModeCrtcPtr crtc = drmModeGetCrtc(fd, encoder->crtc_id);
drmModeFBPtr fb = drmModeGetFB(fd, crtc->buffer_id);

在上一个例子中我们将图形绘制在了gbm_surface上,这里就从gbm_surface创建一个新的drmModeFBPtr然后取代原来的命令行drmModeFBPtr给drmModeCrtcPtr显示:

struct gbm_bo *bo = gbm_surface_lock_front_buffer(gs);
uint32_t my_fb;
drmModeAddFB(fd, gbm_bo_get_handle(bo).u32, &my_fb);
drmModeSetCrtc(fd, crtc->crtc_id, my_fb);

完整实例如下

(在自己Ubutun主机测试运行成功)

1.无显示服务器的OpenGL绘制,将渲染的输出保存为PNG图像文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

#include <fcntl.h>
#include <unistd.h>

#include <gbm.h>
#include <png.h>

// #include <epoxy/gl.h>
// #include <epoxy/egl.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>

GLuint program;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
struct gbm_device *gbm;
struct gbm_surface *gs;

#define TARGET_SIZE 256

static EGLConfig get_config(void)
{
	EGLint egl_config_attribs[] = {
		EGL_BUFFER_SIZE,	32,
		EGL_DEPTH_SIZE,		EGL_DONT_CARE,
		EGL_STENCIL_SIZE,	EGL_DONT_CARE,
		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
		EGL_SURFACE_TYPE,	EGL_WINDOW_BIT,
		EGL_NONE,
	};

	EGLint num_configs;
	assert(eglGetConfigs(display, NULL, 0, &num_configs) == EGL_TRUE);

	EGLConfig *configs = malloc(num_configs * sizeof(EGLConfig));
	assert(eglChooseConfig(display, egl_config_attribs,
			       configs, num_configs, &num_configs) == EGL_TRUE);
	assert(num_configs);
	printf("num config %d\n", num_configs);

	// Find a config whose native visual ID is the desired GBM format.
	for (int i = 0; i < num_configs; ++i) {
		EGLint gbm_format;

		assert(eglGetConfigAttrib(display, configs[i],
					  EGL_NATIVE_VISUAL_ID, &gbm_format) == EGL_TRUE);
		printf("gbm format %x\n", gbm_format);

		if (gbm_format == GBM_FORMAT_ARGB8888) {
			EGLConfig ret = configs[i];
			free(configs);
			return ret;
		}
	}

	// Failed to find a config with matching GBM format.
	abort();
}

//初始化 EGL 和 GBM,创建 EGL 表面和上下文,并将它们绑定到当前线程,以便进行 OpenGL ES 的渲染操作
static void render_target_init(void)
{
//	assert(epoxy_has_egl_extension(EGL_NO_DISPLAY, "EGL_MESA_platform_gbm"));
	//打开渲染设备文件 /dev/dri/renderD128 
	int fd = open("/dev/dri/renderD128", O_RDWR);
	assert(fd >= 0);
	//创建 GBM 设备
	gbm = gbm_create_device(fd);
	assert(gbm != NULL);

//	display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, gbm, NULL);
	//获取 EGL 显示
    display = eglGetDisplay (gbm);
	assert(display != EGL_NO_DISPLAY);

	//初始化 EGL,并检查 EGL 版本
	EGLint majorVersion;
	EGLint minorVersion;
	assert(eglInitialize(display, &majorVersion, &minorVersion) == EGL_TRUE);

	// /绑定 EGL API 为 OpenGL ES
	assert(eglBindAPI(EGL_OPENGL_ES_API) == EGL_TRUE);

	//获取合适的 EGL 配置
	EGLConfig config = get_config();
	//创建 GBM surface并将其赋值给变量 gs。
	gs = gbm_surface_create(
		gbm, TARGET_SIZE, TARGET_SIZE, GBM_BO_FORMAT_ARGB8888,
		GBM_BO_USE_LINEAR|GBM_BO_USE_SCANOUT|GBM_BO_USE_RENDERING);
	assert(gs);

	// surface = eglCreatePlatformWindowSurfaceEXT(display, config, gs, NULL);
	// /创建 EGL 表面并将其赋值给变量 surface。
	surface = eglCreateWindowSurface (display, config, gs, NULL);
	assert(surface != EGL_NO_SURFACE);

	const EGLint contextAttribs[] = {
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};
	//创建 EGL 上下文并将其赋值给变量 context。
	context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
	assert(context != EGL_NO_CONTEXT);
	//将 EGL 表面和上下文绑定到当前线程。
	assert(eglMakeCurrent(display, surface, surface, context) == EGL_TRUE);
}

//编译指定类型的着色器源码,并返回相应的着色器对象
static GLuint compile_shader(const char *source, GLenum type)
{
	GLuint shader;
	GLint compiled;

	shader = glCreateShader(type);
	glShaderSource(shader, 1, &source, NULL);
	glCompileShader(shader);

	glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
	if (!compiled) {
		GLint infoLen = 0;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
		if (infoLen > 1) {
			char *infoLog = malloc(infoLen);
			glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
			fprintf(stderr, "Error compiling shader:\n%s\n", infoLog);
			free(infoLog);
		}
		glDeleteShader(shader);
		return 0;
	}

	return shader;
}

//定义了两个字符串常量 vertex_shader 和 fragment_shader,分别表示顶点着色器和片段着色器的源码。
static const char vertex_shader[] =
	"attribute vec3 positionIn;"
	"void main() {"
	"    gl_Position = vec4(positionIn, 1);"
	"}";

static const char fragment_shader[] =
	"void main() {"
	"    gl_FragColor = vec4(1.0, 0.0, 0.0, 1);"
	"}";


//初始化 OpenGL ES,并创建、编译、链接着色器程序
static void init_gles(void)
{
	GLint linked;
	GLuint vertexShader;
	GLuint fragmentShader;
	assert((vertexShader = compile_shader(vertex_shader, GL_VERTEX_SHADER)) != 0);
	assert((fragmentShader = compile_shader(fragment_shader, GL_FRAGMENT_SHADER)) != 0);
	assert((program = glCreateProgram()) != 0);
	glAttachShader(program, vertexShader);
	glAttachShader(program, fragmentShader);
	glLinkProgram(program);
	glGetProgramiv(program, GL_LINK_STATUS, &linked);
	if (!linked) {
		GLint infoLen = 0;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
		if (infoLen > 1) {
			char *infoLog = malloc(infoLen);
			glGetProgramInfoLog(program, infoLen, NULL, infoLog);
			fprintf(stderr, "Error linking program:\n%s\n", infoLog);
			free(infoLog);
		}
		glDeleteProgram(program);
		exit(1);
	}

	glClearColor(0, 0, 0, 0);
	glViewport(0, 0, TARGET_SIZE, TARGET_SIZE);

	glUseProgram(program);
}

//执行渲染操作
static void render(void)
{
	GLfloat vertex[] = {
		-1, -1, 0,
		-1, 1, 0,
		1, 1, 0,
	};

	GLint position = glGetAttribLocation(program, "positionIn");
	glEnableVertexAttribArray(position);
	glVertexAttribPointer(position, 3, GL_FLOAT, 0, 0, vertex);

	glClear(GL_COLOR_BUFFER_BIT);

	glDrawArrays(GL_TRIANGLES, 0, 3);

	//通过调用 eglSwapBuffers 函数将渲染的结果显示在屏幕上
	eglSwapBuffers(display, surface);
}

//将图像数据写入PNG文件
static int write_image(char* filename, int width, int height, int stride,
		       void *buffer, char* title, bool swap_rb)
{
	int code = 0;
	FILE *fp = NULL;
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;

	// Open file for writing (binary mode)
	fp = fopen(filename, "wb");
	if (fp == NULL) {
		fprintf(stderr, "Could not open file %s for writing\n", filename);
		code = 1;
		goto finalise;
	}

	// Initialize write structure
	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (png_ptr == NULL) {
		fprintf(stderr, "Could not allocate write struct\n");
		code = 1;
		goto finalise;
	}

	// Initialize info structure
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) {
		fprintf(stderr, "Could not allocate info struct\n");
		code = 1;
		goto finalise;
	}

	// Setup Exception handling
	if (setjmp(png_jmpbuf(png_ptr))) {
		fprintf(stderr, "Error during png creation\n");
		code = 1;
		goto finalise;
	}

	png_init_io(png_ptr, fp);

	// Write header (8 bit colour depth)
	png_set_IHDR(png_ptr, info_ptr, width, height,
		     8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
		     PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

	// Set title
	if (title != NULL) {
		png_text title_text;
		title_text.compression = PNG_TEXT_COMPRESSION_NONE;
		title_text.key = "Title";
		title_text.text = title;
		png_set_text(png_ptr, info_ptr, &title_text, 1);
	}

	if (swap_rb)
		png_set_bgr(png_ptr);

	png_write_info(png_ptr, info_ptr);

	// Write image data
	int i;
	for (i = 0; i < height; i++)
		png_write_row(png_ptr, (png_bytep)buffer + i * stride);

	// End write
	png_write_end(png_ptr, NULL);

finalise:
	if (fp != NULL) fclose(fp);
	if (info_ptr != NULL) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
	if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL);

	return code;
}

//将渲染的输出保存为图像文件
static void dump_output(void)
{
	/*在第一个分支(#if 0)中,使用了OpenGL的函数glReadPixels来读取当前帧缓冲区的像素数据。
	读取的数据存储在名为result的缓冲区中,该缓冲区的大小为TARGET_SIZE * TARGET_SIZE * 4字节
	(每个像素4个字节,包括RGBA通道)。然后调用write_image函数将数据保存为PNG图像文件。
	函数的参数包括图像文件名、宽度、高度、每行字节的步幅、像素数据缓冲区、标题和是否交换红蓝通道的标志。*/	
#if 0
	GLubyte result[TARGET_SIZE * TARGET_SIZE * 4] = {0};
	glReadPixels(0, 0, TARGET_SIZE, TARGET_SIZE, GL_RGBA, GL_UNSIGNED_BYTE, result);
	assert(glGetError() == GL_NO_ERROR);

	assert(!write_image("screenshot.png", TARGET_SIZE, TARGET_SIZE,
			    TARGET_SIZE * 4, result, "hello", false));
#else
	/*在第二个分支中,使用了GBM(Generic Buffer Manager)的函数来获取前端缓冲区的数据。
	首先通过gbm_surface_lock_front_buffer获取GBM的缓冲对象bo,然后使用gbm_bo_map映射缓冲对象的数据,
	并返回映射后的指针map_data和步幅stride。获取到的数据存储在名为result的缓冲区中。
	接着调用write_image函数将数据保存为PNG图像文件,参数与前一个分支相同。
	最后,使用gbm_bo_unmap解除映射并释放GBM缓冲对象。*/
	struct gbm_bo *bo = gbm_surface_lock_front_buffer(gs);
	assert(bo);

	uint32_t stride;
	void *map_data = NULL;
	GLubyte *result = gbm_bo_map(
		bo, 0, 0, TARGET_SIZE, TARGET_SIZE,
		GBM_BO_TRANSFER_READ, &stride, &map_data);
	assert(result);

	assert(!write_image("screenshot.png", TARGET_SIZE, TARGET_SIZE,
			    stride, result, "hello", true));

	gbm_bo_unmap(bo, map_data);
	gbm_surface_release_buffer(gs, bo);
#endif
}

int main(void)
{
	render_target_init();
	init_gles();
	render();

	// dump output to a png file
	dump_output();

	return 0;
}

//gcc egl_test2.c -o egl_test2 -lgbm -lEGL -lGLESv2 -lpng

渲染结果为一张红色三角形的PNG图片

2.显示器显示帧缓冲

例1:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <fcntl.h>
#include <unistd.h>

#include <gbm.h>
#include <png.h>

#include <EGL/egl.h>
#include <GLES2/gl2.h>

#include <xf86drm.h>
#include <xf86drmMode.h>

GLuint program;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
struct gbm_device *gbm;
struct gbm_surface *gs;

int drm_fd;
drmModeConnectorPtr connector = NULL;
drmModeFBPtr fb;
drmModeCrtcPtr crtc;
int display_width;
int display_height;

static void display_init(void)
{
	int fd = open("/dev/dri/card0", O_RDWR);
	assert(fd >= 0);

	drmModeResPtr res = drmModeGetResources(fd);
	assert(res);

	for (int i = 0; i < res->count_connectors; i++) {
		connector = drmModeGetConnector(fd, res->connectors[i]);
		assert(connector);

		// find a connected connection
		if (connector->connection == DRM_MODE_CONNECTED)
			break;

		drmFree(connector);
		connector = NULL;
	}
	assert(connector);

	drmModeEncoderPtr encoder = drmModeGetEncoder(fd, connector->encoder_id);
	assert(encoder);

	crtc = drmModeGetCrtc(fd, encoder->crtc_id);
	assert(crtc);

	// original fb used for terminal
	fb = drmModeGetFB(fd, crtc->buffer_id);
	assert(fb);

	drm_fd = fd;
	display_width = fb->width;
	display_height = fb->height;

	drmFree(encoder);
	drmFree(res);
}

static EGLConfig get_config(void)
{
	EGLint egl_config_attribs[] = {
		EGL_BUFFER_SIZE,	32,
		EGL_DEPTH_SIZE,		EGL_DONT_CARE,
		EGL_STENCIL_SIZE,	EGL_DONT_CARE,
		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
		EGL_SURFACE_TYPE,	EGL_WINDOW_BIT,
		EGL_NONE,
	};

	EGLint num_configs;
	assert(eglGetConfigs(display, NULL, 0, &num_configs) == EGL_TRUE);

	EGLConfig *configs = malloc(num_configs * sizeof(EGLConfig));
	assert(eglChooseConfig(display, egl_config_attribs,
			       configs, num_configs, &num_configs) == EGL_TRUE);
	assert(num_configs);
	printf("num config %d\n", num_configs);

	// Find a config whose native visual ID is the desired GBM format.
	for (int i = 0; i < num_configs; ++i) {
		EGLint gbm_format;

		assert(eglGetConfigAttrib(display, configs[i],
					  EGL_NATIVE_VISUAL_ID, &gbm_format) == EGL_TRUE);
		printf("gbm format %x\n", gbm_format);

		if (gbm_format == GBM_FORMAT_ARGB8888) {
			EGLConfig ret = configs[i];
			free(configs);
			return ret;
		}
	}

	// Failed to find a config with matching GBM format.
	abort();
}

static void render_target_init(void)
{
	// assert(epoxy_has_egl_extension(EGL_NO_DISPLAY, "EGL_MESA_platform_gbm"));

	gbm = gbm_create_device(drm_fd);
	assert(gbm != NULL);

    display = eglGetDisplay (gbm);
	assert(display != EGL_NO_DISPLAY);

	EGLint majorVersion;
	EGLint minorVersion;
	assert(eglInitialize(display, &majorVersion, &minorVersion) == EGL_TRUE);

	assert(eglBindAPI(EGL_OPENGL_ES_API) == EGL_TRUE);

	EGLConfig config = get_config();

	gs = gbm_surface_create(
		gbm, display_width, display_height, GBM_BO_FORMAT_ARGB8888,
		GBM_BO_USE_LINEAR|GBM_BO_USE_SCANOUT|GBM_BO_USE_RENDERING);
	assert(gs);

    surface = eglCreateWindowSurface (display, config, gs, NULL);
	// surface = eglCreatePlatformWindowSurfaceEXT(display, config, gs, NULL);
	assert(surface != EGL_NO_SURFACE);

	const EGLint contextAttribs[] = {
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};
	context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
	assert(context != EGL_NO_CONTEXT);

	assert(eglMakeCurrent(display, surface, surface, context) == EGL_TRUE);
}

static GLuint compile_shader(const char *source, GLenum type)
{
	GLuint shader;
	GLint compiled;

	shader = glCreateShader(type);
	glShaderSource(shader, 1, &source, NULL);
	glCompileShader(shader);

	glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
	if (!compiled) {
		GLint infoLen = 0;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
		if (infoLen > 1) {
			char *infoLog = malloc(infoLen);
			glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
			fprintf(stderr, "Error compiling shader:\n%s\n", infoLog);
			free(infoLog);
		}
		glDeleteShader(shader);
		return 0;
	}

	return shader;
}

static const char vertex_shader[] =
	"attribute vec3 positionIn;"
	"void main() {"
	"    gl_Position = vec4(positionIn, 1);"
	"}";

static const char fragment_shader[] =
	"void main() {"
	"    gl_FragColor = vec4(1.0, 0.0, 0.0, 1);"
	"}";

static void init_gles(void)
{
	GLint linked;
	GLuint vertexShader;
	GLuint fragmentShader;
	assert((vertexShader = compile_shader(vertex_shader, GL_VERTEX_SHADER)) != 0);
	assert((fragmentShader = compile_shader(fragment_shader, GL_FRAGMENT_SHADER)) != 0);
	assert((program = glCreateProgram()) != 0);
	glAttachShader(program, vertexShader);
	glAttachShader(program, fragmentShader);
	glLinkProgram(program);
	glGetProgramiv(program, GL_LINK_STATUS, &linked);
	if (!linked) {
		GLint infoLen = 0;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
		if (infoLen > 1) {
			char *infoLog = malloc(infoLen);
			glGetProgramInfoLog(program, infoLen, NULL, infoLog);
			fprintf(stderr, "Error linking program:\n%s\n", infoLog);
			free(infoLog);
		}
		glDeleteProgram(program);
		exit(1);
	}

	glClearColor(0, 0, 0, 0);
	glViewport(0, 0, display_width, display_height);

	glUseProgram(program);
}

static void render(void)
{
	GLfloat vertex[] = {
		-1, -1, 0,
		-1, 1, 0,
		1, 1, 0,
	};

	GLint position = glGetAttribLocation(program, "positionIn");
	glEnableVertexAttribArray(position);
	glVertexAttribPointer(position, 3, GL_FLOAT, 0, 0, vertex);

	glClear(GL_COLOR_BUFFER_BIT);

	glDrawArrays(GL_TRIANGLES, 0, 3);

	eglSwapBuffers(display, surface);
}

//在显示器上展示渲染输出,通过将前端缓冲对象转换为帧缓冲并设置为当前显示内容来实现。
static void display_output(void)
{	
	//首先,通过调用gbm_surface_lock_front_buffer函数获取GBM的前端缓冲对象bo
	struct gbm_bo *bo = gbm_surface_lock_front_buffer(gs);
	assert(bo);

        uint32_t my_fb;
	//接下来,使用drmModeAddFB函数将前端缓冲对象转换为DRM(Direct Rendering Manager)的帧缓冲(Framebuffer)。
	assert(!drmModeAddFB(drm_fd, gbm_bo_get_width(bo),
			     gbm_bo_get_height(bo), 24,
			     gbm_bo_get_bpp(bo),
			     gbm_bo_get_stride(bo),
			     gbm_bo_get_handle(bo).u32,
			     &my_fb));

	// show my_fb
	//然后,通过调用drmModeSetCrtc函数将帧缓冲设置为显示器的当前模式。
	assert(!drmModeSetCrtc(drm_fd, crtc->crtc_id, my_fb, 0, 0,
			       &connector->connector_id, 1, &crtc->mode));

	// hold on for a moment
	sleep(10);

	// restore previous fb
	//通过再次调用drmModeSetCrtc函数将之前的帧缓冲恢复为当前显示的内容。函数的参数与前一个调用相同。
	assert(!drmModeSetCrtc(drm_fd, crtc->crtc_id, fb->fb_id, 0, 0,
			       &connector->connector_id, 1, &crtc->mode));
	//最后,通过调用gbm_surface_release_buffer函数释放前端缓冲对象。
	gbm_surface_release_buffer(gs, bo);
}

int main(void)
{
	// init display
	display_init();

	// render to fb
	render_target_init();
	init_gles();
	render();

	// show on screen
        display_output();

	return 0;
}

//gcc egl_test1.c -o egl_test1 -lgbm -lEGL -lGLESv2 -ldrm

结果为在屏幕上显示一个红色三角形

例2:

// gcc -o drm-gbm drm-gbm.c -ldrm -lgbm -lEGL -lGL -I/usr/include/libdrm

// general documentation: man drm

#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
#include <EGL/egl.h>
#include <GL/gl.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>

#define EXIT(msg) { fputs (msg, stderr); exit (EXIT_FAILURE); }

static int device;

//在给定的drmModeRes资源中查找并返回第一个连接的drmModeConnector对象
static drmModeConnector *find_connector (drmModeRes *resources) {
	// 遍历connectors
	int i;
	for (i=0; i<resources->count_connectors; i++) {
		//在每次循环迭代中,通过调用drmModeGetConnector函数来获取指定连接器ID的连接器对象。
		drmModeConnector *connector = drmModeGetConnector (device, resources->connectors[i]);
		// 检查获取到的连接器对象是否已连接。选择第一个连接的连接器
		if (connector->connection == DRM_MODE_CONNECTED) {
			return connector;
		}
		//在每次循环迭代后,使用drmModeFreeConnector函数释放先前获取的连接器对象的内存,以避免内存泄漏。
		drmModeFreeConnector (connector);
	}
	// 没有找到连接器
	return NULL;
}

//在给定的drmModeRes资源和drmModeConnector连接器对象中查找并返回与连接器关联的编码器对象drmModeEncoder。
static drmModeEncoder *find_encoder (drmModeRes *resources, drmModeConnector *connector) {
	if (connector->encoder_id) {
		/*如果connector->encoder_id非零,表示连接器有关联的编码器,则通过调用drmModeGetEncoder函数
		来获取指定编码器ID的编码器对象。函数的参数device是一个设备指针,用于获取编码器对象。*/
		return drmModeGetEncoder (device, connector->encoder_id);
	}
	// 没有找到编码器
	return NULL;
}

static uint32_t connector_id;
static drmModeModeInfo mode_info;
static drmModeCrtc *crtc;

static void find_display_configuration () {
	//首先调用drmModeGetResources函数获取系统的显示资源信息。
	drmModeRes *resources = drmModeGetResources (device);
	// 调用find_connector函数找到一个连接器。
	drmModeConnector *connector = find_connector (resources);
	if (!connector) EXIT ("no connector found\n");
	// 将找到的连接器的ID保存到connector_id中。
	connector_id = connector->connector_id;
	// 保存连接器的第一个模式信息到mode_info变量中,并打印分辨率信息。
	mode_info = connector->modes[0];
	printf ("resolution: %ix%i\n", mode_info.hdisplay, mode_info.vdisplay);
	// 找到一个encoder
	drmModeEncoder *encoder = find_encoder (resources, connector);
	if (!encoder) EXIT ("no encoder found\n");
	// 获取CRTC
	if (encoder->crtc_id) {
		crtc = drmModeGetCrtc (device, encoder->crtc_id);
	}
	//释放资源
	drmModeFreeEncoder (encoder);
	drmModeFreeConnector (connector);
	drmModeFreeResources (resources);
}

static struct gbm_device *gbm_device;
static EGLDisplay display;
static EGLContext context;
static struct gbm_surface *gbm_surface;
static EGLSurface egl_surface;

//根据指定的EGL配置属性数组,查询系统中可用的EGL配置,并根据特定的GBM格式选择合适的配置
static EGLConfig get_config(void)
{
	//
	EGLint egl_config_attribs[] = {
		EGL_BUFFER_SIZE,	32,   //EGL_BUFFER_SIZE:指定缓冲区的位数为32位。
		EGL_DEPTH_SIZE,		EGL_DONT_CARE,  //EGL_DEPTH_SIZE:深度缓冲区大小,使用EGL_DONT_CARE表示不关心具体大小。
		EGL_STENCIL_SIZE,	EGL_DONT_CARE,  //EGL_STENCIL_SIZE:模板缓冲区大小
		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT, //EGL_RENDERABLE_TYPE:指定可渲染的类型为OpenGL ES 2.0
		EGL_SURFACE_TYPE,	EGL_WINDOW_BIT, //EGL_SURFACE_TYPE:指定表面类型为窗口。
		EGL_NONE,  //EGL_NONE:属性数组的结束标志。
	};

	EGLint num_configs;   //调用eglGetConfigs函数获取系统中可用的EGL配置数量,将结果保存在num_configs变量中
	assert(eglGetConfigs(display, NULL, 0, &num_configs) == EGL_TRUE);

	EGLConfig *configs = malloc(num_configs * sizeof(EGLConfig));
	/*调用eglChooseConfig函数,根据指定的属性数组egl_config_attribs选择系统中满足条件的EGL配置,
	并将配置保存在configs中。同时更新num_configs变量为实际选中的配置数量。*/
	assert(eglChooseConfig(display, egl_config_attribs,
			       configs, num_configs, &num_configs) == EGL_TRUE);
	assert(num_configs);      //确保至少存在一个满足条件的配置
	printf("num config %d\n", num_configs);

	// 循环遍历选中的配置,使用eglGetConfigAttrib函数获取每个配置的native visual ID(GBM格式),保存在gbm_format变量中
	for (int i = 0; i < num_configs; ++i) {
		EGLint gbm_format;

		assert(eglGetConfigAttrib(display, configs[i],
					  EGL_NATIVE_VISUAL_ID, &gbm_format) == EGL_TRUE);
		printf("gbm format %x\n", gbm_format);
		/*如果找到与目标GBM格式(GBM_FORMAT_ARGB8888)匹配的配置,
		即gbm_format等于目标格式,就释放configs的内存并返回该配置。*/
		if (gbm_format == GBM_FORMAT_ARGB8888) {
			EGLConfig ret = configs[i];
			free(configs);
			return ret;
		}
	}
	// 未找到匹配的配置,调用abort函数终止程序。
	abort();
}

//创建一个基于GBM和EGL的OpenGL环境,以便后续进行图形渲染和操作。
static void setup_opengl () {
	//创建GBM设备对象,用于与底层图形设备交互。
	gbm_device = gbm_create_device (device);
	// 获取与GBM设备关联的EGL显示对象
	display = eglGetDisplay (gbm_device);
	//初始化EGL显示对象
	eglInitialize (display, NULL, NULL);
	
	// 绑定OpenGL API
	eglBindAPI (EGL_OPENGL_API);

	EGLConfig config;
	config = get_config();
	// 创建OpenGL上下文对象。
	context = eglCreateContext (display, config, EGL_NO_CONTEXT, NULL);
	
	// create the GBM and EGL surface
    //创建GBM表面对象,指定了表面的宽度、高度、像素格式以及使用方式。
	gbm_surface = gbm_surface_create (gbm_device, mode_info.hdisplay, mode_info.vdisplay, GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_LINEAR|GBM_BO_USE_SCANOUT|GBM_BO_USE_RENDERING);
	//创建EGL窗口表面对象,将GBM表面与EGL绑定
	egl_surface = eglCreateWindowSurface (display, config, gbm_surface, NULL);
	//将OpenGL上下文与EGL表面进行绑定,使其成为当前上下文。
	eglMakeCurrent (display, egl_surface, egl_surface, context);
}

static struct gbm_bo *previous_bo = NULL;
static uint32_t previous_fb;

static void swap_buffers () {
	//交换EGL表面的前后缓冲区。
	eglSwapBuffers (display, egl_surface);
	//锁定GBM表面的前端缓冲区,以便进行后续操作。
	struct gbm_bo *bo = gbm_surface_lock_front_buffer (gbm_surface);
	//获取前端缓冲区的句柄
	uint32_t handle = gbm_bo_get_handle (bo).u32;
	//获取前端缓冲区的行距
	uint32_t pitch = gbm_bo_get_stride (bo);
	//定义帧缓冲区的标识符
	uint32_t fb;
	//创建一个新的帧缓冲区,设置其分辨率、像素格式、行距等属性,并将前端缓冲区的句柄关联到帧缓冲区。
	drmModeAddFB (device, mode_info.hdisplay, mode_info.vdisplay, 24, 32, pitch, handle, &fb);
	//将帧缓冲区与CRTC(显示控制器)关联,将帧缓冲区的内容显示在屏幕上。
	drmModeSetCrtc (device, crtc->crtc_id, fb, 0, 0, &connector_id, 1, &mode_info);

	// drmModeAddFB(device, gbm_bo_get_width(bo),
	// 		     gbm_bo_get_height(bo), 24,
	// 		     gbm_bo_get_bpp(bo),
	// 		     gbm_bo_get_stride(bo),
	// 		     gbm_bo_get_handle(bo).u32,
	// 		     &fb);
	// drmModeSetCrtc(device, crtc->crtc_id, fb, 0, 0,
	// 		       &connector_id, 1, &mode_info);

	if (previous_bo) {  //检查是否存在前一个帧缓冲区对象。
		//删除前一个帧缓冲区
		drmModeRmFB (device, previous_fb);
		//释放前一个GBM表面的缓冲区。
		gbm_surface_release_buffer (gbm_surface, previous_bo);
	}
	//更新前一个帧缓冲区对象为当前帧缓冲区对象
	previous_bo = bo;
	//更新前一个帧缓冲区的标识符为当前帧缓冲区的标识符
	previous_fb = fb;
	/*这里注意:
		eglSwapBuffers函数负责交换前后缓冲区,将当前渲染的内容从后台缓冲区切换到前台缓冲区。
	这个过程通常发生在GPU内部,不涉及将内容直接显示到屏幕上。
		然后,通过调用drmModeAddFB函数,将前台缓冲区(通过struct gbm_bo对象获取的相关信息)关联到帧缓冲区
	(framebuffer),以便将其内容传输到显示设备。帧缓冲区是用于实际显示的内存区域。
		最后,通过调用drmModeSetCrtc函数,配置显示控制器(CRTC)以将帧缓冲区的内容显示在屏幕上。
	这个函数告诉显示设备使用特定的帧缓冲区来更新显示内容,从而实现将内容真正显示到屏幕上。
		因此,eglSwapBuffers只是将渲染的内容从后台缓冲区切换到前台缓冲区,
	而drmModeAddFB和drmModeSetCrtc是将帧缓冲区的内容与显示设备相关联并实际显示到屏幕上的步骤。
	*/
}

//显示绘制内容
static void draw (float progress) {
	glClearColor (1.0f-progress, progress, 0.0, 1.0);
	glClear (GL_COLOR_BUFFER_BIT);
	swap_buffers ();
}

static void clean_up () {
	// 将CRTC设置为之前保存的状态。这将还原之前的显示设置,包括缓冲区和模式等。
	drmModeSetCrtc (device, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, &connector_id, 1, &crtc->mode);
	drmModeFreeCrtc (crtc);
	
	//释放之前使用的缓冲区资源。通过drmModeRmFB从设备中移除之前创建的帧缓冲区,并使用gbm_surface_release_buffer释放之前的GBM缓冲区。
	if (previous_bo) {
		drmModeRmFB (device, previous_fb);
		gbm_surface_release_buffer (gbm_surface, previous_bo);
	}
	
	eglDestroySurface (display, egl_surface);
	gbm_surface_destroy (gbm_surface);
	eglDestroyContext (display, context);
	eglTerminate (display);
	gbm_device_destroy (gbm_device);
}

int main () {
	//打开设备文件
	device = open ("/dev/dri/card0", O_RDWR);
	//查找显示设备的配置信息,如连接器、编码器、CRTC等
	find_display_configuration ();
	//设置OpenGL上下文、GBM和EGL surface等。
	setup_opengl ();
	
	/*绘制动画:使用for循环进行600次迭代,每次调用draw函数绘制动画帧。
	它使用OpenGL函数设置清除颜色并清除颜色缓冲区,然后调用swap_buffers函数交换缓冲区。*/
	int i;
	for (i = 0; i < 600; i++)
		draw (i / 600.0f);
	
	//清理和释放在程序运行期间所创建的资源,包括恢复显示配置、释放缓冲区、销毁EGL和GBM资源等。
	clean_up ();
	close (device);
	return 0;
}

结果为在屏幕上显示渐变的颜色

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值