OpenGL 离屏烘托(Off-screen Rendering),短答案:
eglCreatePbufferSurface()
glReadPixels()
TLDR
EGL 1.5 的 2 个扩展
界说了一个独立于详细窗口体系(如:X11, Wayland, Windows)的笼统设备类别 EGLDeviceEXT
。大致而言,一个 EGLDeviceEXT
object 代表一个 GPU 设备。各个详细的窗口体系(X11, Wayland…)及 EGLDeviceEXT
在 EGL 规范中统称“platform”。指定 platform,调用 eglGetPlatformDisplay()
函数可创立针对此 platform 的 EGLDisplay
object,进一步创立 EGLSurface
和 EGLContext
object,供 OpenGL 烘托。
根据 EGLDeviceEXT
的应用程序,不依赖于平台的窗口体系 API,例如,不需求在 X11 或 Wayland 图形环境下运转,适合 headless 体系下的图画烘托,且利用了 OpenGL 供给的硬件加速。
下面按先后顺序介绍用到的 API。
首先需检查 EGL 完成是否支撑前述扩展。调用 EGL 函数 eglQueryString()
并供给 EGL_NO_DISPLAY
参数可查询 EGL 支撑的悉数扩展。如下:
const char *s = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (NULL == strstr(s, "EGL_EXT_platform_device") || NULL == strstr(s, "EGL_EXT_device_base"))
{
printf("EGL extension EXT_platform_device not supportedn");
exit(-1);
}
要运用 EGL 扩展,程序需包含 <EGL/eglext.h>
头文件,并调用 eglGetProcAddress()
函数获取扩展函数的函数指针/地址。EGL_EXT_device_base 扩展中界说了一个 eglQueryDevicesEXT()
函数是后面需求用到的,必须先获取其函数地址,如下:
#include <EGL/egl.h>
#include <EGL/eglext.h>
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT;
...
eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
接下来可调用此函数获取支撑的 EGLDeviceEXT
object:
EGLDeviceEXT egl_device;
{
EGLint num;
eglQueryDevicesEXT(0, NULL, &num);
if (0 == num)
{
...
}
eglQueryDevicesEXT(1, &egl_device, &num);
}
然后通过 EGLDeviceEXT
object 创立 EGLDisplay
object,并初始化:
EGLDisplay egl_disp = eglGetPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, egl_device, NULL);
if (EGL_NO_DISPLAY == egl_disp)
{
...
}
if (!eglInitialize(egl_disp, NULL, NULL))
{
...
}
接下来创立 pbuffer surface。Pbuffer surface 不同于 window surface,用于 off screen 烘托。通常 pbuffer 用作 OpenGL texture object,但也可以直接将其复制到应用程序地址空间,例如,保存为图片文件。创立 pbuffer surface 代码如下:
#define WINDOW_WIDTH 480
#define WINDOW_HEIGHT 480
...
EGLConfig egl_config;
{
const EGLint attr_list[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, //
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, //
EGL_RED_SIZE, 5, //
EGL_GREEN_SIZE, 6, //
EGL_BLUE_SIZE, 5, //
EGL_DEPTH_SIZE, 8, //
EGL_NONE, //
};
EGLint num;
if (!eglChooseConfig(egl_disp, attr_list, &egl_config, 1, &num) || 1 != num)
{
...
}
}
EGLSurface egl_surf;
{
EGLint attr_list[] = {
EGL_WIDTH, WINDOW_WIDTH, //
EGL_HEIGHT, WINDOW_HEIGHT, //
EGL_NONE, //
};
egl_surf = eglCreatePbufferSurface(egl_disp, egl_config, attr_list);
if (EGL_NO_SURFACE == egl_surf)
{
...
}
}
特别留意上面代码第 10, 11 行。
最后,创立 EGLContext
object,并将其设为当时:
EGLContext egl_context;
{
EGLint attr_list[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, //
EGL_NONE, //
};
egl_context = eglCreateContext(egl_disp, egl_config, EGL_NO_CONTEXT, attr_list);
if (EGL_NO_CONTEXT == egl_context)
{
...
}
}
eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_context);
至此,EGL 初始化完毕,可以进行 OpenGL 烘托,并调用 OpenGL glReadPixels()
函数从 pbuffer 中将图画/像素数据复制到 CPU 地址空间:
GLubyte buf[3 * WINDOW_WIDTH * WINDOW_HEIGHT];
glReadPixels(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, buf);
完整的示例程序代码在 gitlab.com/sihokk/lear…