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/
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的主要功能包括:
Mesa GBM在Linux图形堆栈中扮演着重要的角色,它作为一个中间层,连接了底层的图形设备和上层的图形组件。它为应用程序、窗口系统和驱动程序提供了一种统一的接口,使它们能够协同工作,实现图形渲染、显示和交互等功能。
我们先从原理上对GBM进行推导。GBM的本质:硬件无关的显存申请与管理。为什么需要这么一个库:DRM自带的DUMB缓冲区不能申请GPU显存,不能使用3D加速。因此,GBM的基本操作就是显存的申请与释放。但是这个管理必有一个上下文,很明显不可能是全局的,因为哪怕是作为用户的我们,也肯定希望申请出的GPU显存是与某个GPU绑定的。这个上下文由struct gbm_device
进行抽象,且必定是与DRM设备绑定的。
struct gbm_device *gbm_create_device(int fd);
从这里可以看到一段示例代码,gbm_create_device
的参数fd实际上就是打开一个DRM设备后得到的文件描述符。注意实际上文件描述符并不一定必须是DRM文件描述符,这是因为GBM是支持多种后端的,gbm_create_device
函数可以在/usr/lib/gbm/
文件夹下找到形如
的后端,然后装载使用(这个主要是给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
进行的操作总结如下:
createNewScreen2
方法,得到__DRIScreen
并保存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。
要理解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(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;
}
在命令行Linux操作系统中实现一个支持GPU图形应用的显示服务器:
这是一个在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去显示。
例二【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主机测试运行成功)
#include
#include
#include
#include
#include
#include
#include
#include
// #include
// #include
#include
#include
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图片
例1:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}
结果为在屏幕上显示渐变的颜色