在一个多窗口的图形化操作系统中,显示服务器的任务就是组织这些窗口展现给用户。实际的显示服务器有Linux世界基于X11协议的XServer或者Wayland协议的Weston,还有Android里的SurfaceFlinger。但是除了显示,他们同时包含了输入管理(鼠标、键盘等)的功能,本文主要还是讨论图形显示部分,通过将显示服务器用到的技术分解,实际来看看如何实现一个显示服务器。
需求分析
在命令行Linux操作系统中实现一个支持GPU图形应用的显示服务器:
- 每个图形应用绘制到一个缓冲(Buffer)
- 图形应用将这个缓冲交给显示服务器组装成帧缓冲(Framebuffer)
- 显示服务器将帧缓冲显示到显示器上
一个简单的OpenGL应用
这是一个在Linux X11系统下简单的OpenGL应用【1】,效果是在一个X11窗口中绘制一个红色三角形。我们看看OpenGL应用是如何跟显示服务器(XServer)交互的。注意这里忽略了无关参数和代码,下同。
Display
首先用EGL从X11的Display和Window创建Context,然后用OpenGL进行实际绘制,最后用EGL的eglSwapBuffers将绘制好的缓冲交给XServer去显示。
这个例子的目的,一是看看OpenGL应用和显示服务器的接口,二是看看在显示服务器那边,我们也想用OpenGL做窗口组装的话还需要什么。
以下三个例子会展示如何解决:
- 在没有显示服务器(自己就是显示服务器)的情况下创建Context进行OpenGL绘制
- 将绘制好的帧缓冲给显示器显示
- 应用将缓冲交给显示服务器去组装
无显示服务器的OpenGL绘制
例二【2】是在没有显示服务器的情况下,用EGL+GBM做OpenGL绘制的例子:
int
GBM是一个GPU缓冲管理的API,可以直接从GPU的设备文件(/dev/dri/renderD128)创建一个gbm_device,然后再创建一个代表GPU缓冲的gbm_surface。支持EGL_MESA_platform_gbm扩展【5】的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
在上一个例子中我们将图形绘制在了gbm_surface上,这里就从gbm_surface创建一个新的drmModeFBPtr然后取代原来的命令行drmModeFBPtr给drmModeCrtcPtr显示:
struct
应用和显示服务器间传递缓冲
现在我们知道自己实现的应用和显示服务器端如何用OpenGL进行绘制,而且显示服务器如何将绘制好的帧缓冲拿去显示。最后的一个问题就是应用如何将缓冲传递给显示服务器用于组装。应用和显示服务器是两个独立进程,这个需求只能用进程间通信来解决,但是图形系统所需传递的数据量巨大(一帧往往需要几MB,每秒60帧,就是上百MB/s,这还只是一个应用), 如果用传统的进程间通信方法比如Socket和System V IPC,性能会很差。所以我们需要一种零拷贝的进程间共享缓冲的方式。
例四【4】展示了使用dma-buf+unix local socket达到应用和显示服务器间零拷贝传递缓冲的实现。里面是应用部分的代码,是显示服务器部分的代码。首先我们创建两个进程分别代表应用和显示服务器,用socketpair建立unix local socket连接(出于演示方便,真实的显示服务器会用完整的socket listen/bind/connect/accept流程):
int
然后是应用端,我们用GBM接口获得缓冲的文件描述(File Descriptor),这个文件描述只是一个句柄,在Linux内核里代表了一个关联缓冲的dma-buf结构体。我们只需要在进程间传递这个文件描述就可以共享他所代表的缓冲而不用拷贝。传递文件描述的方法就是这个Unix Local Socket的sendmsg函数。
// 获得缓冲
最后是显示服务器端,用recvmsg接收应用传送来的文件描述,然后还原为gbm_bo。支持EGL_KHR_image_pixmap扩展【6】的EGL可以将gbm_bo转换为EGLImageKHR(对于支持EGL_EXT_image_dma_buf_import扩展【7】的EGL可以直接将文件描述转换为EGLImageKHR)。支持GL_OES_EGL_image扩展【8】的OpenGL可以将EGLImageKHR转换为贴图,这样就能将应用的缓冲作为一个OpengGL的贴图进行组装成帧缓冲的绘图操作了。
// 接收dma-buf
结语
本文介绍的这些技术是显示服务器实现的基础,下一篇文章我们会讨论更深入的应用和显示服务器间的同步问题。
引用
- 例一:OpenGL应用
- 例二:无显示服务器OpenGL
- 例三:显示帧缓冲
- 例四:缓冲传递
- EGL_MESA_platform_gbm扩展
- EGL_KHR_image_pixmap扩展
- EGL_EXT_image_dma_buf_import扩展
- GL_OES_EGL_image扩展