如何使用GLUT/OpenGL渲染到文件?

28

我有一个模拟物理系统的程序,该系统会随着时间的推移而变化。我想要在预定的时间间隔(比如每10秒)将模拟状态的可视化输出到文件中。我希望以这样一种方式来完成它,即轻松地“关闭可视化”,并且不输出任何可视化内容。

我考虑使用OpenGL和GLUT作为图形工具来进行可视化。然而,问题似乎是首先,它似乎只能输出到窗口,无法输出到文件。其次,在生成可视化时,您必须调用GLUTMainLoop,这会停止主函数的执行 - 从那时起,只有GUI的调用才会被调用。但是,我不希望这是一个基于GUI的应用程序 - 我只希望它是一个从命令行运行并生成一系列图像的应用程序。是否有办法在GLUT / OpenGL中实现这一点?或者OpenGL完全是错误的工具,我应该使用其他工具?


如果你要写模拟状态而不是可视化,那么模拟状态有多大? - Phil H
6个回答

76

glReadPixels 可运行的 PBO 示例

以下示例生成以下内容之一:

  • 每帧以200 FPS产生一个ppm图像,无需额外的依赖;
  • 每帧以600 FPS使用libpng生成一个png图像;
  • 以1200 FPS使用FFmpeg生成所有帧的一个mpg视频。

这些操作均在ramfs上进行。压缩得越好,FPS就越大,所以我们必须受到内存I/O限制

在我的60 FPS屏幕上,FPS大于200,并且所有图像都是不同的,所以我确定它不仅仅限制于屏幕的FPS。

本答案中的GIF动画是根据视频生成的,方法请参考:https://askubuntu.com/questions/648603/how-to-create-an-animated-gif-from-mp4-video-via-command-line/837574#837574

glReadPixels是关键的OpenGL函数,用于从屏幕读取像素。还请查看init()下的设置。

glReadPixels 函数首先读取底部像素行,这与大多数图像格式不同,因此通常需要进行转换。

offscreen.c

#ifndef PPM
#define PPM 1
#endif
#ifndef LIBPNG
#define LIBPNG 1
#endif
#ifndef FFMPEG
#define FFMPEG 1
#endif

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define GL_GLEXT_PROTOTYPES 1
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/glext.h>

#if LIBPNG
#include <png.h>
#endif

#if FFMPEG
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#endif

enum Constants { SCREENSHOT_MAX_FILENAME = 256 };
static GLubyte *pixels = NULL;
static GLuint fbo;
static GLuint rbo_color;
static GLuint rbo_depth;
static int offscreen = 1;
static unsigned int max_nframes = 128;
static unsigned int nframes = 0;
static unsigned int time0;
static unsigned int height = 128;
static unsigned int width = 128;
#define PPM_BIT (1 << 0)
#define LIBPNG_BIT (1 << 1)
#define FFMPEG_BIT (1 << 2)
static unsigned int output_formats = PPM_BIT | LIBPNG_BIT | FFMPEG_BIT;

/* Model. */
static double angle;
static double delta_angle;

#if PPM
/* Take screenshot with glReadPixels and save to a file in PPM format.
 *
 * -   filename: file path to save to, without extension
 * -   width: screen width in pixels
 * -   height: screen height in pixels
 * -   pixels: intermediate buffer to avoid repeated mallocs across multiple calls.
 *     Contents of this buffer do not matter. May be NULL, in which case it is initialized.
 *     You must `free` it when you won't be calling this function anymore.
 */
static void screenshot_ppm(const char *filename, unsigned int width,
        unsigned int height, GLubyte **pixels) {
    size_t i, j, cur;
    const size_t format_nchannels = 3;
    FILE *f = fopen(filename, "w");
    fprintf(f, "P3\n%d %d\n%d\n", width, height, 255);
    *pixels = realloc(*pixels, format_nchannels * sizeof(GLubyte) * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, *pixels);
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            cur = format_nchannels * ((height - i - 1) * width + j);
            fprintf(f, "%3d %3d %3d ", (*pixels)[cur], (*pixels)[cur + 1], (*pixels)[cur + 2]);
        }
        fprintf(f, "\n");
    }
    fclose(f);
}
#endif

#if LIBPNG
/* Adapted from https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/png/open_manipulate_write.c */
static png_byte *png_bytes = NULL;
static png_byte **png_rows = NULL;
static void screenshot_png(const char *filename, unsigned int width, unsigned int height,
        GLubyte **pixels, png_byte **png_bytes, png_byte ***png_rows) {
    size_t i, nvals;
    const size_t format_nchannels = 4;
    FILE *f = fopen(filename, "wb");
    nvals = format_nchannels * width * height;
    *pixels = realloc(*pixels, nvals * sizeof(GLubyte));
    *png_bytes = realloc(*png_bytes, nvals * sizeof(png_byte));
    *png_rows = realloc(*png_rows, height * sizeof(png_byte*));
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels);
    for (i = 0; i < nvals; i++)
        (*png_bytes)[i] = (*pixels)[i];
    for (i = 0; i < height; i++)
        (*png_rows)[height - i - 1] = &(*png_bytes)[i * width * format_nchannels];
    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png) abort();
    png_infop info = png_create_info_struct(png);
    if (!info) abort();
    if (setjmp(png_jmpbuf(png))) abort();
    png_init_io(png, f);
    png_set_IHDR(
        png,
        info,
        width,
        height,
        8,
        PNG_COLOR_TYPE_RGBA,
        PNG_INTERLACE_NONE,
        PNG_COMPRESSION_TYPE_DEFAULT,
        PNG_FILTER_TYPE_DEFAULT
    );
    png_write_info(png, info);
    png_write_image(png, *png_rows);
    png_write_end(png, NULL);
    png_destroy_write_struct(&png, &info);
    fclose(f);
}
#endif

#if FFMPEG
/* Adapted from: https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/ffmpeg/encode.c */

static AVCodecContext *c = NULL;
static AVFrame *frame;
static AVPacket pkt;
static FILE *file;
static struct SwsContext *sws_context = NULL;
static uint8_t *rgb = NULL;

static void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *rgb) {
    const int in_linesize[1] = { 4 * c->width };
    sws_context = sws_getCachedContext(sws_context,
            c->width, c->height, AV_PIX_FMT_RGB32,
            c->width, c->height, AV_PIX_FMT_YUV420P,
            0, NULL, NULL, NULL);
    sws_scale(sws_context, (const uint8_t * const *)&rgb, in_linesize, 0,
            c->height, frame->data, frame->linesize);
}

void ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height) {
    AVCodec *codec;
    int ret;
    avcodec_register_all();
    codec = avcodec_find_encoder(codec_id);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }
    c->bit_rate = 400000;
    c->width = width;
    c->height = height;
    c->time_base.num = 1;
    c->time_base.den = fps;
    c->gop_size = 10;
    c->max_b_frames = 1;
    c->pix_fmt = AV_PIX_FMT_YUV420P;
    if (codec_id == AV_CODEC_ID_H264)
        av_opt_set(c->priv_data, "preset", "slow", 0);
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
    file = fopen(filename, "wb");
    if (!file) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = c->pix_fmt;
    frame->width  = c->width;
    frame->height = c->height;
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate raw picture buffer\n");
        exit(1);
    }
}

void ffmpeg_encoder_finish(void) {
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };
    int got_output, ret;
    do {
        fflush(stdout);
        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }
        if (got_output) {
            fwrite(pkt.data, 1, pkt.size, file);
            av_packet_unref(&pkt);
        }
    } while (got_output);
    fwrite(endcode, 1, sizeof(endcode), file);
    fclose(file);
    avcodec_close(c);
    av_free(c);
    av_freep(&frame->data[0]);
    av_frame_free(&frame);
}

void ffmpeg_encoder_encode_frame(uint8_t *rgb) {
    int ret, got_output;
    ffmpeg_encoder_set_frame_yuv_from_rgb(rgb);
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    ret = avcodec_encode_video2(c, &pkt, frame, &got_output);
    if (ret < 0) {
        fprintf(stderr, "Error encoding frame\n");
        exit(1);
    }
    if (got_output) {
        fwrite(pkt.data, 1, pkt.size, file);
        av_packet_unref(&pkt);
    }
}

void ffmpeg_encoder_glread_rgb(uint8_t **rgb, GLubyte **pixels, unsigned int width, unsigned int height) {
    size_t i, j, k, cur_gl, cur_rgb, nvals;
    const size_t format_nchannels = 4;
    nvals = format_nchannels * width * height;
    *pixels = realloc(*pixels, nvals * sizeof(GLubyte));
    *rgb = realloc(*rgb, nvals * sizeof(uint8_t));
    /* Get RGBA to align to 32 bits instead of just 24 for RGB. May be faster for FFmpeg. */
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels);
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            cur_gl  = format_nchannels * (width * (height - i - 1) + j);
            cur_rgb = format_nchannels * (width * i + j);
            for (k = 0; k < format_nchannels; k++)
                (*rgb)[cur_rgb + k] = (*pixels)[cur_gl + k];
        }
    }
}
#endif

static void model_init(void) {
    angle = 0;
    delta_angle = 1;
}

static int model_update(void) {
    angle += delta_angle;
    return 0;
}

static int model_finished(void) {
    return nframes >= max_nframes;
}

static void init(void)  {
    int glget;

    if (offscreen) {
        /*  Framebuffer */
        glGenFramebuffers(1, &fbo);
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);

        /* Color renderbuffer. */
        glGenRenderbuffers(1, &rbo_color);
        glBindRenderbuffer(GL_RENDERBUFFER, rbo_color);
        /* Storage must be one of: */
        /* GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8. */
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, width, height);
        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo_color);

        /* Depth renderbuffer. */
        glGenRenderbuffers(1, &rbo_depth);
        glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth);

        glReadBuffer(GL_COLOR_ATTACHMENT0);

        /* Sanity check. */
        assert(glCheckFramebufferStatus(GL_FRAMEBUFFER));
        glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glget);
        assert(width < (unsigned int)glget);
        assert(height < (unsigned int)glget);
    } else {
        glReadBuffer(GL_BACK);
    }

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);

    time0 = glutGet(GLUT_ELAPSED_TIME);
    model_init();
#if FFMPEG
    ffmpeg_encoder_start("tmp.mpg", AV_CODEC_ID_MPEG1VIDEO, 25, width, height);
#endif
}

static void deinit(void)  {
    printf("FPS = %f\n", 1000.0 * nframes / (double)(glutGet(GLUT_ELAPSED_TIME) - time0));
    free(pixels);
#if LIBPNG
    if (output_formats & LIBPNG_BIT) {
        free(png_bytes);
        free(png_rows);
    }
#endif
#if FFMPEG
    if (output_formats & FFMPEG_BIT) {
        ffmpeg_encoder_finish();
        free(rgb);
    }
#endif
    if (offscreen) {
        glDeleteFramebuffers(1, &fbo);
        glDeleteRenderbuffers(1, &rbo_color);
        glDeleteRenderbuffers(1, &rbo_depth);
    }
}

static void draw_scene(void) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glRotatef(angle, 0.0f, 0.0f, -1.0f);
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f( 0.0f,  0.5f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-0.5f, -0.5f, 0.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f( 0.5f, -0.5f, 0.0f);
    glEnd();
}

static void display(void) {
    char filename[SCREENSHOT_MAX_FILENAME];
    draw_scene();
    if (offscreen) {
        glFlush();
    } else {
        glutSwapBuffers();
    }
#if PPM
    if (output_formats & PPM_BIT) {
        snprintf(filename, SCREENSHOT_MAX_FILENAME, "tmp.%d.ppm", nframes);
        screenshot_ppm(filename, width, height, &pixels);
    }
#endif
#if LIBPNG
    if (output_formats & LIBPNG_BIT) {
        snprintf(filename, SCREENSHOT_MAX_FILENAME, "tmp.%d.png", nframes);
        screenshot_png(filename, width, height, &pixels, &png_bytes, &png_rows);
    }
#endif
# if FFMPEG
    if (output_formats & FFMPEG_BIT) {
        frame->pts = nframes;
        ffmpeg_encoder_glread_rgb(&rgb, &pixels, width, height);
        ffmpeg_encoder_encode_frame(rgb);
    }
#endif
    nframes++;
    if (model_finished())
        exit(EXIT_SUCCESS);
}

static void idle(void) {
    while (model_update());
    glutPostRedisplay();
}

int main(int argc, char **argv) {
    int arg;
    GLint glut_display;

    /* CLI args. */
    glutInit(&argc, argv);
    arg = 1;
    if (argc > arg) {
        offscreen = (argv[arg][0] == '1');
    } else {
        offscreen = 1;
    }
    arg++;
    if (argc > arg) {
        max_nframes = strtoumax(argv[arg], NULL, 10);
    }
    arg++;
    if (argc > arg) {
        width = strtoumax(argv[arg], NULL, 10);
    }
    arg++;
    if (argc > arg) {
        height = strtoumax(argv[arg], NULL, 10);
    }
    arg++;
    if (argc > arg) {
        output_formats = strtoumax(argv[arg], NULL, 10);
    }

    /* Work. */
    if (offscreen) {
        /* TODO: if we use anything smaller than the window, it only renders a smaller version of things. */
        /*glutInitWindowSize(50, 50);*/
        glutInitWindowSize(width, height);
        glut_display = GLUT_SINGLE;
    } else {
        glutInitWindowSize(width, height);
        glutInitWindowPosition(100, 100);
        glut_display = GLUT_DOUBLE;
    }
    glutInitDisplayMode(glut_display | GLUT_RGBA | GLUT_DEPTH);
    glutCreateWindow(argv[0]);
    if (offscreen) {
        /* TODO: if we hide the window the program blocks. */
        /*glutHideWindow();*/
    }
    init();
    glutDisplayFunc(display);
    glutIdleFunc(idle);
    atexit(deinit);
    glutMainLoop();
    return EXIT_SUCCESS;
}

在 GitHub 上

使用以下命令进行编译:

sudo apt-get install libpng-dev libavcodec-dev libavutil-dev
gcc -DPPM=1 -DLIBPNG=1 -DFFMPEG=1 -ggdb3 -std=c99 -O0 -Wall -Wextra \
  -o offscreen offscreen.c -lGL -lGLU -lglut -lpng -lavcodec -lswscale -lavutil

运行10个帧的“离屏”(主要是待办事项,可以工作但没有优势),尺寸为200 x 100,并支持所有输出格式:
./offscreen 1 10 200 100 7

CLI 格式为:

./offscreen [offscreen [nframes [width [height [output_formats]]]]]

output_formats 是一个位掩码:

ppm >> 0 | png >> 1 | mpeg >> 2

在屏幕上运行(不限制我的FPS):

./offscreen 0

在Ubuntu 15.10,OpenGL 4.4.0 NVIDIA 352.63,Lenovo Thinkpad T430上进行了基准测试。
还在Ubuntu 18.04,OpenGL 4.6.0 NVIDIA 390.77,Lenovo Thinkpad P51上进行了测试。
待办事项:找到一种在没有GUI(例如X11)的机器上进行操作的方法。似乎OpenGL不适用于离屏渲染,并且将像素读回GPU的操作是通过窗口系统接口(例如GLX)实现的。参见:Linux中无X.org的OpenGL

TODO:使用1x1窗口,使其不可调整大小,并隐藏它以使事情更加健壮。如果我执行其中任何一个操作,则渲染将失败,请参见代码注释。防止调整大小在Glut中似乎不可能,但GLFW支持它。无论如何,这些都不重要,因为即使offscreen关闭时,我的FPS也不受屏幕刷新频率的限制。

PBO之外的其他选项

  • 渲染到后台缓冲区(默认渲染位置)
  • 渲染到纹理
  • 渲染到Pixelbuffer对象(PBO)

FramebufferPixelbuffer比后台缓冲区和纹理更好,因为它们是为数据读取到CPU而设计的,而后台缓冲区和纹理则是为保留在GPU上并显示在屏幕上而设计的。

PBO 用于异步传输,所以我认为我们不需要它,参见:OpenGL 中帧缓冲对象和像素缓冲对象之间的区别是什么?

也许离屏 Mesa 值得一看:http://www.mesa3d.org/osmesa.html

Vulkan

似乎 Vulkan 的设计比 OpenGL 更好地支持离屏渲染。

这在 NVIDIA 的概述中提到:https://developer.nvidia.com/transitioning-opengl-vulkan

这是一个可运行的示例,我刚刚在本地成功运行: https://github.com/SaschaWillems/Vulkan/tree/b9f0ac91d2adccc3055a904d3a8f6553b10ff6cd/examples/renderheadless/renderheadless.cpp

安装驱动程序并确保 GPU 正常工作后,我可以进行以下操作:

git clone https://github.com/SaschaWillems/Vulkan
cd Vulkan
git checkout b9f0ac91d2adccc3055a904d3a8f6553b10ff6cd
python download_assets.py
mkdir build
cd build
cmake ..
make -j`nproc`
cd bin
./renderheadless

这将立即生成一个图像headless.ppm,而不需要打开任何窗口:

enter image description here

我还成功地在Ubuntu Ctrl + Alt + F3非图形TTY上运行了这个程序,这进一步表明它确实不需要屏幕。

其他可能感兴趣的示例:

相关:Vulkan中是否可以进行无Surface的离屏渲染?

在Ubuntu 20.04、NVIDIA驱动程序435.21和NVIDIA Quadro M1200 GPU上测试通过。

apiretrace

https://github.com/apitrace/apitrace

只需要简单操作,无需修改您的代码即可正常运行:

git clone https://github.com/apitrace/apitrace
cd apitrace
git checkout 7.0
mkdir build
cd build
cmake ..
make
# Creates opengl_executable.out.trace
./apitrace trace /path/to/opengl_executable.out
./apitrace dump-images opengl_executable.out.trace

同样适用于Ubuntu 18.10:

 sudo apt-get install apitrace

现在你有一堆以以下名称命名的屏幕截图:

animation.out.<n>.png

待办事项:工作原理。

文档还建议在视频中使用此方法:

apitrace dump-images -o - application.trace \
  | ffmpeg -r 30 -f image2pipe -vcodec ppm -i pipe: -vcodec mpeg4 -y output.mp4

另请参阅:

WebGL + 画布图像保存

由于性能问题,这主要是一种玩具,但它在某些基本用例中还是有点作用的:

参考文献

FBO 大于窗口大小:

无窗口 / X11:


2
png_write_end(png, NULL);这行之后,你应该使用类似于png_destroy_write_struct(&png, &info);的语句来释放PNG结构。感谢您提供如此方便的代码! - mgmalheiros
@mgmalheiros 好的,Marcelo! - Ciro Santilli OurBigBook.com

12

无论如何,您几乎肯定不想要GLUT。它的设计目的与您的要求不符(即使您的要求符合其预期用途,通常也不需要它)。

您可以使用OpenGL。要在文件中生成输出,您基本上需要设置OpenGL来渲染纹理,然后将生成的纹理读入主存储器并保存到文件中。至少在某些系统(例如Windows)上,我相当确定您仍然需要创建窗口并将渲染上下文与窗口关联,但如果窗口始终处于隐藏状态,则可能会没问题。


2
不是要否定其他出色的答案,但如果您想要一个现有的例子,我们在OpenSCAD中已经进行了几年的离屏GL渲染,作为测试框架从命令行生成.png文件的一部分。相关文件位于https://github.com/openscad/openscad/tree/master/src下的Offscreen*.cc。

它可以在OSX(CGL),Linux X11(GLX),BSD(GLX)和Windows(WGL)上运行,但由于驱动程序的差异,存在一些怪癖。基本技巧是忘记打开窗口(就像Douglas Adams说的飞行技巧是忘记撞地面一样)。如果您有一个虚拟的X11服务器运行,比如Xvfb或Xvnc,它甚至可以在“无头”linux / bsd上运行。还有可能在Linux / BSD上使用软件渲染,方法是在运行程序之前设置环境变量LIBGL_ALWAYS_SOFTWARE = 1,这在某些情况下可能有所帮助。

这不是唯一的系统,我认为VTK成像系统也做了类似的事情。

这段代码的方法有点陈旧(我从Brian Paul的glxgears中剥离了GLX代码),尤其是随着新系统的出现,如OSMesa,Mir,Wayland,EGL,Android,Vulkan等等,但请注意OffscreenXXX.cc文件名,其中XXX是GL上下文子系统,理论上可以移植到不同的上下文生成器。


2
提供一个最小可运行示例,我会点赞的 :-) - Ciro Santilli OurBigBook.com

1

不确定OpenGL是最佳解决方案。
但你总可以渲染到一个离屏缓冲区。

将OpenGL的输出写入文件的典型方式是使用readPixels将结果场景逐像素复制到图像文件中。



0

1. 使用PbufferSurface而不是WindowSurface使用EGL进行离屏渲染

EGLDisplay display;
EGLSurface surface;
EGLContext context;

//bind desktop OpenGL
eglBindAPI(EGL_OPENGL_API);
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, nullptr, nullptr);

//example constraints
const EGLint egl_config_constraints[] = {
    EGL_STENCIL_SIZE, static_cast<EGLint>(8),
    EGL_DEPTH_SIZE, static_cast<EGLint>(16),
    EGL_BUFFER_SIZE, static_cast<EGLint>(32),
    EGL_RED_SIZE, static_cast<EGLint>(8),
    EGL_GREEN_SIZE, static_cast<EGLint>(8),
    EGL_BLUE_SIZE, static_cast<EGLint>(8),
    EGL_ALPHA_SIZE, static_cast<EGLint>(8),
    EGL_SAMPLE_BUFFERS, EGL_FALSE,
    EGL_SAMPLES, 0,
    EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
    EGL_CONFORMANT, EGL_OPENGL_BIT,
    EGL_CONFIG_CAVEAT, EGL_NONE,
    EGL_NONE };

EGLint configCount;
EGLConfig configs[1];

//find a fitting config
eglChooseConfig(display, egl_config_constraints, configs, 1, &configCount);

//set up the PbufferSurface
EGLint pbuffer_attrib_list[] = {
    EGL_WIDTH, WIDTH,
    EGL_HEIGHT, HEIGHT,
    EGL_NONE };
    
surface = eglCreatePbufferSurface(display, configs[0], pbuffer_attrib_list);

//setup the EGLContext
const EGLint contextVersion[] = {
    EGL_CONTEXT_MAJOR_VERSION, 4,
    EGL_CONTEXT_MINOR_VERSION, 6,
    EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
    EGL_CONTEXT_OPENGL_DEBUG, debug ? EGL_TRUE : EGL_FALSE,
    EGL_NONE };
context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, contextVersion);
eglMakeCurrent(display, surface, surface, context);
eglSwapInterval(display, 1);

2. 保存图像

有几种方法可以实现这一点。实际上,上面的代码是我编写的一个演示(https://github.com/kallaballa/GCV/blob/main/src/tetra/tetra-demo.cpp),它使用OpenCL/OpenGL/VAAPI互操作与OpenCV结合使用,将OpenGL中呈现的内容写入视频。

如果您想构建演示,请注意README。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接