一.什么是Mesa和GLX

众所周知,OpenGL作为图形界的工业标准,其仅仅定义了一组2D和3D图形接口API,而对于窗口管理、IO消息响应等并没有规定。也就是说,OpenGL依赖各平台提供用于渲染的context以及具体实现方式,而各平台提供的实现不尽相同。这些实现主要有:Windows平台下的WGL、Linux下的Mesa/GLX、Mac OS X下的Cocoa/NSGL,以及跨平台的GLUT、GLFW、SDL等等。

Mesa是Linux下的OpenGL实现。它提供了对AMD Radeon系列、Nvidia GPU、Intel i965, i945, i915以及VMWare虚拟GPU等多种硬件驱动的支持,同时也提供了对softpipe等多种软件驱动的支持。Mesa项目由Brian Paul于1993年8月创建,于1995年2月发布了第一个发行版,此后便受到越来越多的关注,如今Mesa已经是任何一个Linux版本首选的OpenGL实现。

GLX则是在Linux上用于提供GL与窗口交互、窗口管理等等的一组API。它的作用与Windows的WGL、Mac OS X的AGL以及针对OpenGL ES的EGL相似。在Linux上,窗口创建、管理等API遵循X Window接口,而GLX提供了OpenGL与X Window交互的办法。因此GLX也可以运用于其他使用X Window的平台,例如FreeBSD等。

二. Mesa和GLX的安装

在Debian/Ubuntu系统上,我们可以使用以下命令来安装Mesa和GLX:

 

1

sudo apt-get install libgl1-mesa-dev

如果希望安装OpenGL ES版本的Mesa,那么就是如下命令:

 

1

sudo apt-get install libgles2-mesa-dev

对于OpenGL ES,EGL的安装如下:

 

1

sudo apt-get install libegl1-mesa-dev

安装完毕以后,可以使用以下命令查看安装的Mesa版本以及安装是否成功:

 

1

glxinfo | grep "OpenGL version"

这里我安装完毕后显示的结果是:

 

1

2

szsilence06@ubuntu:~$ glxinfo | grep "OpenGL version"

OpenGL version string: 3.0 Mesa 11.2.0

 

三. 第一个Mesa程序

3.1 CMake构建

这里我们采用CMake来构建项目。在CMakeLists.txt内加入如下代码来包含mesa:

 

1

2

3

4

find_package(OpenGL REQUIRED)

 

INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIRS})

LINK_DIRECTORIES(${OPENGL_LIBRARY_DIRS})

如下代码负责包含X Window:

 

1

find_package(X11 REQUIRED)

最后链接库文件:

 

1

2

3

4

target_link_libraries(mesa_test

    ${OPENGL_LIBRARIES}

    ${X11_LIBRARIES}

)

这里mesa_test是我创建的可执行文件名称,读者应当换成自己设定的名称。

3.2 创建窗口

Mesa依赖GLX来为其提供渲染的context。而在继续讨论GLX前,我们需要简单了解一下X Window。X Window支持客户端-服务器模型,也就是说,X Server和X Window可以分别运行于不同的机器上,从而允许我们远程运行桌面系统。因此,在渲染之前,我们必须了解程序将在哪个显示器上进行渲染。

我们可以使用如下函数来获取显示器:

 

1

Display* display = XOpenDisplay(getenv("DISPLAY"));

当然,在此之前,我们需要包含相应的头文件:

 

1

2

3

#include <GL/gl.h>

#include <GL/glx.h>

#include <stdlib.h>

在程序退出前,我们需要关闭到显示器的连接:

 

1

XCloseDisplay(display);

在获取显示器以后,我们就可以创建窗口了。创建窗口使用XCreateWindow函数,这个函数的原型如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

Window XCreateWindow(

    Display* /* display */,

    Window /* parent */,

    int /* x */,

    int /* y */,

    unsigned int /* width */,

    unsigned int /* height */,

    unsigned int /* border_width */,

    int /* depth */,

    unsigned int /* class */,

    Visual* /* visual */,

    unsigned long /* valuemask */,

    XSetWindowAttributes* /* attributes */

);

各参数的意义我不列出了,请自行google,这里我给一个简单的调用示例:

 

1

2

3

4

5

6

7

8

9

int screen = DefaultScreen(display);

    int width = 640;

    int height = 480;

    int screen_width = DisplayWidth(display, screen);

    int screen_height = DisplayHeight(display, screen);

 

    window = XCreateWindow(display, XRootWindow(display, screen), (screen_width - width) / 2,

                                  (screen_height - height) / 2, width, height, 0, CopyFromParent,

                                  InputOutput, DefaultVisual(display, screen), 0, nullptr);

创建好窗口之后,还要调用以下函数将窗口显示出来:

 

1

XMapWindow(display,window);

 

3.3 创建OpenGL环境

有了窗口以后,下一步就是创建context。在创建Context之前,我们需要先对帧缓存进行配置。以下函数可以得到当前屏幕的帧缓存配置信息:

 

1

2

    int configNum;

    GLXFBConfig* config = glXGetFBConfigs(display, screen, &configNum);

GLXFBConfig是一个包含大量配置项的结构体,configNum即为函数返回的GLXFBConfig中的配置项数目。为简单起见,我这里就不去修改这个config里面的配置项了。然后我们需要从这个配置项建立一个XVisualInfo,这是X Window用来描述显示参数的数据结构:

 

1

XVisualInfo* visualInfo = glXGetVisualFromFBConfig(display, *config);

然后,我们终于可以创建OpenGL环境了:

 

1

GLXContext context = glXCreateContext(display, visualInfo, nullptr, true);

最后将该环境设为当前绘制环境:

 

1

glXMakeCurrent(display, window, context);

 

3.4 消息循环

熟悉Windows编程的同学应该对消息循环都不陌生。以上我们写好的程序如果运行一下,会发现窗口一闪而过然后程序就退出了。显然这是因为我们还没有为窗口添加消息循环的缘故。

X Window的消息与Windows有一些不同。由于X Window是基于客户端-服务器模型的,因此我们可以自由选择想要处理的消息,这样可以节省网络带宽。以下函数就是负责选择窗口想要处理的消息的:

 

1

2

3

4

5

int XSelectInput(

    Display* /* display */,

    Window /* w */,

    long /* event_mask */

);

event_mask有很多种,具体我不列出了。以下代码可以侦听所有的消息:

 

1

2

int event_mask = (1 << 25) - 1;

XSelectInput(display, window, event_mask);  //listen to all types of events

设置好要侦听的消息以后,我们可以调用以下函数来获取消息:

 

1

2

XEvent e;

XNextEvent(display, &e);

XNextEvent是一个阻塞式函数,它可以获取XEvent消息。我们可以根据XEvent的type字段判断消息的类型:

 

1

2

3

switch (e.type) {

//...

}

各种XEvent中,我们这里需要侦听Expose消息,这个消息在窗口绘制的时候触发,我们应当在这里处理我们的渲染。此外,我们还需要侦听窗口退出消息,好让我们可以进行程序退出前的一些处理操作。然而,X Window标准并没有为窗口退出定义一个消息类型。想要侦听到窗口退出消息的话,一种方法如下所示:

 

1

2

Atom wmDelete = XInternAtom(display, "WM_DELETE_WINDOW", True);

XSetWMProtocols(display, window, &wmDelete, 1);

这样,当窗口退出时,程序就会发送ClientMessage消息。于是,一个基本的消息循环就如下所示:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

bool needQuit = false;

while(needQuit == false) {

    XEvent e;

    XNextEvent(display, &e);

    switch (e.type) {

    case Expose:

        Render();

        break;

    case ClientMessage:

        needQuit = true;

        break;

    default:

        break;

    }

}

 

3.5 绘制

在以上工作都做好之后,我们就可以按通常的OpenGL编程方式编写绘制代码了。例如,创建好环境以后进行如下初始化设置:

 

1

2

3

4

5

6

glViewport(0, 0, width, height);

glClearColor(0, 0, 0, 1);

glMatrixMode(GL_PROJECTION);

glOrtho(0, width, 0, height, 0, 1000);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

绘制一个三角形:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

void Render()

{

    glClear(GL_COLOR_BUFFER_BIT);

 

    glBegin(GL_TRIANGLES);

    glColor3f(1.0f, 0.0f, 0.0f);

    glVertex2f(400,400);

    glColor3f(0.0f, 1.0f, 0.0f);

    glVertex2f(400,200);

    glColor3f(0.0f, 0.0f, 1.0f);

    glVertex2f(200,200);

    glEnd();

 

    glFlush();

    glXSwapBuffers(display, window);

}

程序运行结果如图。

untitled

3.6 收尾工作

程序退出前要记得以下收尾工作:

 

1

2

3

glXDestroyWindow(display, glxWindow);

XDestroyWindow(display, window);

XCloseDisplay(display);

 

四.总结

本文简要介绍了简要介绍了Linux下OpenGL程序的写法。可以看到这种基于X Window的写法还是很繁琐的,因此如果要做Linux上的图形程序开发的话,还是尽量选用GLUT、GLFW、SDL和Qt这样的封装好的环境比较好。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐