基于linux系统的OpenGL环境

简介

相关资料有
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;
}

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值