简介
相关资料有
khronos.org/egl
nvidia developer blog
OpenGL without X.org in linux
PyOpenGL headless rendering
khronos论坛
EGL函数文档
linux系统对于图形渲染的支持是非常复杂繁琐的,对于初学者,人都会看晕了,这其中涉及很多东西如GL,EGL,GLX,WGL,AGL,XGL,XGLX,GLUT,GLFW,GL3W,GLEW,GLAD,GLU,GLM,X11,Xming,Xmanager,Xserver,wayland,Vulkan。
这里很多东西网上可以找到资料,只说几个关键的地方。因为我是想在linux上做图形渲染,但是linux是没装显示器的,很多图形程序是无法运行的,这称之为离屏渲染或延迟渲染,或者叫headless模式。对于初学者小白,在windows系统上安装OpenGL是比较简单的,但是在没有显示器的linux上做OpenGL离屏渲染,初学者难免一时不太清楚如何配置环境,这里简单介绍一下。
linux系统对于图形或者窗口系统的支持,来自于X11系统,其将窗口系统分成两部分,分别是X server和X client,X client的运行结果传递到X server上显示出来,所以可以很方便的进行远程桌面显示。这种方式一度很先进很时髦,但是后来,这种方式方式显的有点繁琐,尤其是对于高性能计算来说,这种方式会降低图形渲染效率。为此,最近开始流行wayland窗口系统,新版的ubuntu也已经支持这种桌面窗口了,这种窗口显示系统将其进行了简化,直接本地渲染传输数据,提升了效率。但是对于nvidia英伟达显卡,wayland的驱动是基于开源社区基于逆向工程而实现的,所以效率比不上其私有驱动,所以可能总的效率也看不出多大提高,而amd显卡则对此有更好的支持。
要使用OpenGL进行离屏渲染,需要创建Framebuffer帧缓存。在窗口系统中创建一个显示窗口的时候会自动创建默认帧缓存,而在离屏渲染的时候,因为没有创建显示窗口,所以需要在程序中手动创建并且配置帧缓存。而配置帧缓存需要配置DISPLAY,config和上下文Context等,这个还是需要窗口系统的底层支持。在原本的linux中可以调用X11的支持,为此需要安装有GLX库,导入方式一般如下所示,之后可以基于GLX来创建Context上下文,在创建Framebuffer帧缓存实现离屏渲染。
#include<GL/glx.h>
GLX创建的Context离不开X11,而除了GLX之外还有EGL,并且EGL不需要使用X11,wayland窗口系统就是基于EGL的,所以wayland可以摆脱X11。所以也可以使用EGL创建Context和Framebuffer帧缓存,之后就可在纯命令行终端下运行OpenGL,实现headless离屏渲染模式了,导入方式很简单。
#include<EGL/egl.h>
我刚开始的时候,不知道这个EGL是咋安装上去的,后来才了解到系统或者显卡驱动会自带,但是有时候也会找不到开发库,所以可能需要另外安装。
yum install mesa*
yum install freeglut
yum install *GLEW*
EGL创建Context
#include <EGL/egl.h>
#include <GL/gl.h>
static const EGLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_NONE
};
//static const int pbufferWidth = 9;
//static const int pbufferHeight = 9;
#define pbufferWidth 9
#define pbufferHeight 9
static const EGLint pbufferAttribs[] = {
EGL_WIDTH, pbufferWidth,
EGL_HEIGHT, pbufferHeight,
EGL_NONE
};
int main(int argc, char *argv[])
{
// 1. Initialize EGL
EGLDisplay eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major, minor;
eglInitialize(eglDpy, &major, &minor);
// 2. Select an appropriate configuration
EGLint numConfigs;
EGLConfig eglCfg;
eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs);
// 3. Create a surface
EGLSurface eglSurf = eglCreatePbufferSurface(eglDpy, eglCfg,
pbufferAttribs);
// 4. Bind the API
eglBindAPI(EGL_OPENGL_API);
// 5. Create a context and make it current
EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT,
NULL);
eglMakeCurrent(eglDpy, eglSurf, eglSurf, eglCtx);
// from now on use your OpenGL context
// 6. Terminate EGL when finished
eglTerminate(eglDpy);
return 0;
}
在使用EGL创建了Context之后,就可以创建Framebuffer帧缓存了,但是直接调用帧缓存创建函数,却显示错误,提示未定义函数glGenFramebuffers。
GLuint Framebuffer;
glGenFramebuffers(1, &Framebuffer);
//编译程序,显示错误
gcc -o test_2 test_2.c -lEGL
/tmp/cchBuBdO.o: In function `main':
test_2.c:(.text+0xc3): undefined reference to `glGenFramebuffers'
collect2: error: ld returned 1 exit status
后来才了解到需要使用基于EGL的函数实现
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL;
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) eglGetProcAddress("glGenFramebuffers");
经过这个进行函数赋值之后,就能调用原先的OpenGL的函数了,当然这个还跟上面的EGL的API绑定有关,因为EGL是可以跨平台的,对于linux,windows和手机移动端都是可以实现的,所以不同的系统平台,要绑定的API也不一样,可以是linux的OpenGL,或者是OpenGL ES等。
eglBindAPI(EGL_OPENGL_API);
但是需要导入很多函数,这时候就会很麻烦了,为此可以专门写一个头文件,用以导入所有要用到的函数,这里只放一个简单的例子。
#include<EGL/egl.h>
#include<GL/gl.h>
#pragma once
// 纹理
PFNGLCREATETEXTURESPROC glCreateTextures = NULL;
PFNGLTEXTURESTORAGE2DPROC glTextureStorage2D = NULL;
// 渲染缓存
PFNGLCREATERENDERBUFFERSPROC glCreateRenderbuffers = NULL;
PFNGLNAMEDRENDERBUFFERSTORAGEPROC glNamedRenderbufferStorage = NULL;
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = NULL;
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = NULL;
// 帧缓存
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL;
PFNGLCREATEFRAMEBUFFERSPROC glCreateFramebuffers = NULL;
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = NULL;
PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC glNamedFramebufferRenderbuffer = NULL;
PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC glCheckNamedFramebufferStatus = NULL;
PFNGLINVALIDATEFRAMEBUFFERPROC glInvalidateFramebuffer = NULL;
PFNGLDRAWBUFFERSPROC glDrawBuffers = NULL;
// 常用
PFNGLENABLEIPROC glEnablei = NULL;
PFNGLVIEWPORTARRAYVPROC glViewportArrayv = NULL;
PFNGLCLEARCOLORXOESPROC glClearColorxoes = NULL;
PFNGLCLEARBUFFERFIPROC glClearBufferi = NULL;
static void load_OpenGLAPI(){
glCreateTextures = (PFNGLCREATETEXTURESPROC) eglGetProcAddress("glCreateTextures");
glTextureStorage2D = (PFNGLTEXTURESTORAGE2DPROC) eglGetProcAddress("glTextureStorage2D");
glCreateRenderbuffers = (PFNGLCREATERENDERBUFFERSPROC) eglGetProcAddress("glCreateRenderbuffers");
glNamedRenderbufferStorage = (PFNGLNAMEDRENDERBUFFERSTORAGEPROC) eglGetProcAddress("glNamedRenderbufferStorage");
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) eglGetProcAddress("glBindRenderbuffer");
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) eglGetProcAddress("glDeleteRenderbuffers");
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) eglGetProcAddress("glGenFramebuffers");
glCreateFramebuffers = (PFNGLCREATEFRAMEBUFFERSPROC) eglGetProcAddress("glCreateFramebuffers");
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) eglGetProcAddress("glBindFramebuffer");
glNamedFramebufferRenderbuffer = (PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC) eglGetProcAddress("glNamedFramebufferRenderbuffer");
glCheckNamedFramebufferStatus = (PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC) eglGetProcAddress("glCheckNamedFramebufferStatus");
glInvalidateFramebuffer = (PFNGLINVALIDATEFRAMEBUFFERPROC) eglGetProcAddress("glInvalidateFramebuffer");
glDrawBuffers = (PFNGLDRAWBUFFERSPROC) eglGetProcAddress("glDrawBuffers");
glEnablei = (PFNGLENABLEIPROC) eglGetProcAddress("glEnablei");
glViewportArrayv = (PFNGLVIEWPORTARRAYVPROC) eglGetProcAddress("glViewportArrayv");
glClearColorxoes = (PFNGLCLEARCOLORXOESPROC) eglGetProcAddress("glClearColorxoes");
glClearBufferi = (PFNGLCLEARBUFFERFIPROC) eglGetProcAddress("glClearBufferi");
}
要继续配置Context和window,可能还不太了解,这里有一份参考代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<EGL/egl.h>
#include<X11/Xlib.h>
struct my_display{
Display *x11;
EGLDisplay egl;
};
struct my_config{
struct my_display dpy;
XVisualInfo *x11;
Colormap colormap;
EGLConfig egl;
};
struct my_window{
struct my_config config;
Window x11;
EGLSurface egl;
};
struct my_pixmap{
struct my_config config;
Pixmap x11;
EGLSurface egl;
};
static void
check_extensions(void){
#ifdef USE_EGL_EXT_PLATFORM_X11
const char *client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if(!client_extensions){
// EGL_EXT_client_extensions is unsupported.
abort();
}
if(!strstr(client_extensions, "EGL_EXT_platform_x11")){
abort();
}
#endif
}
static struct my_display
get_display(void){
struct my_display dpy;
dpy.x11 = XOpenDisplay(NULL);
if(!dpy.x11){
abort();
}
#ifdef USE_EGL_EXT_PLATFORM_X11
dpy.egl = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, dpy.x11, NULL);
#else
dpy.egl = eglGetDisplay(dpy.x11);
#endif
if(dpy.egl == EGL_NO_DISPLAY){
abort();
}
return dpy;
}
static struct my_config
get_config(struct my_display dpy){
struct my_config config = {
.dpy = dpy,
};
EGLint egl_config_attribs[] = {
EGL_BUFFER_SIZE, 32,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
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_PIXMAP_BIT,
EGL_NONE,
};
EGLint num_configs;
if(!eglChooseConfig(dpy.egl,
egl_config_attribs,
&config.egl, 1,
&num_configs)){
abort();
}
if(num_configs == 0){
abort();
}
XVisualInfo x11_visual_info_template;
if(!eglGetConfigAttrib(dpy.egl,
config.egl,
EGL_NATIVE_VISUAL_ID,
(EGLint*)
&x11_visual_info_template.visualid)){
abort();
}
int num_visuals;
config.x11 = XGetVisualInfo(dpy.x11,
VisualIDMask,
&x11_visual_info_template,
&num_visuals);
if(!config.x11){
abort();
}
config.colormap = XCreateColormap(dpy.x11,
RootWindow(dpy.x11, 0),
config.x11->visual,
AllocNone);
if(config.colormap == None){
abort();
}
return config;
}
static struct my_window
get_window(struct my_config config){
XSetWindowAttributes attr;
unsigned long mask;
struct my_window window = {
.config = config,
};
attr.colormap = config.colormap;
mask = CWColormap;
window.x11 = XCreateWindow(config.dpy.x11,
DefaultRootWindow(config.dpy.x11), // parent
0, 0, // x, y
256, 256, // width, height
0, //border_width
config.x11->depth,
InputOutput, // class
config.x11->visual,
mask, // valuemask
&attr); // attributes
if(!window.x11){
abort();
}
#ifdef USE_EGL_EXT_PLATFORM_X11
window.egl = eglCreatePlatformWindowSurfaceEXT(config.dpy.egl,
config.egl,
&window.x11,
NULL);
#else
window.egl = eglCreateWindowSurface(config.dpy.egl,
config.egl,
window.x11,
NULL);
#endif
if(window.egl == EGL_NO_SURFACE){
abort();
}
return window;
}
static struct my_pixmap
get_pixmap(struct my_config config){
struct my_pixmap pixmap = {
.config = config,
};
pixmap.x11 = XCreatePixmap(config.dpy.x11,
DefaultRootWindow(config.dpy.x11),
256, 256, // width, height
config.x11->depth);
if(!pixmap.x11){
abort();
}
#ifdef USE_EGL_EXT_PLATFORM_X11
pixmap.egl = eglCreatePlatformPixmapSurfaceEXT(config.dpy.egl,
config.egl,
&pixmap.x11,
NULL);
#else
pixmap.egl = eglCreatePixmapSurface(config.dpy.egl,
config.egl,
pixmap.x11,
NULL);
#endif
if(pixmap.egl == EGL_NO_SURFACE){
abort();
}
return pixmap;
}
int main()
{
check_extensions();
struct my_display dpy = get_display();
struct my_config config = get_config(dpy);
struct my_window window = get_window(config);
struct my_pixmap pixmap = get_pixmap(config);
printf("Hello world\n");
return 0;
}