pbuffer vs fbo in egl offscreen rendering

时间:2015-03-02 19:20:19

标签: opengl-es egl off-screen

我对egl pbuffer表面非常困惑。在我看来,pbuffer表面是一个独立于平台的表面,就像窗口表面或像素图表面一样。吸引到那个表面的东西虽然不可见,但应该可以回读。

这个问题的答案似乎证实了我的理解:

Difference from eglCreatePbufferSurface and eglCreatePixmapSurface with OpenGL ES(EGL)

但是,我的实验表明,除了使用pbuffer表面外,我还需要创建一个fbo缓冲区。

这段代码似乎对我有用,它会创建一个pbuffer表面然后再创建一个fbo。

#include <GLES2/gl2.h>
#include <EGL/egl.h>

int main(int argc, char *argv[])
{
    EGLint ai32ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2,
                                                     EGL_NONE };
    // Step 1 - Get the default display.
    EGLDisplay eglDisplay = eglGetDisplay((EGLNativeDisplayType)0);
    // Step 2 - Initialize EGL.
    eglInitialize(eglDisplay, 0, 0);
    // Step 3 - Make OpenGL ES the current API.
    eglBindAPI(EGL_OPENGL_ES_API);
    // Step 4 - Specify the required configuration attributes.
    EGLint pi32ConfigAttribs[5];
    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_WINDOW_BIT;
    pi32ConfigAttribs[2] = EGL_RENDERABLE_TYPE;
    pi32ConfigAttribs[3] = EGL_OPENGL_ES2_BIT;
    pi32ConfigAttribs[4] = EGL_NONE;
    // Step 5 - Find a config that matches all requirements.
    int iConfigs;
    EGLConfig eglConfig;
    eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs);

    if (iConfigs != 1) 
    {
        printf("Error: eglChooseConfig(): config not found.\n");
        exit(-1);
    }

    // Step 6 - Create a surface to draw to.
    EGLSurface eglSurface;
    eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, NULL);

    // Step 7 - Create a context.
    EGLContext eglContext;
    eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs);

    // Step 8 - Bind the context to the current thread
    eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

    GLuint fboId = 0;
    GLuint renderBufferWidth = 1920;
    GLuint renderBufferHeight = 1080;

    // Step 9 - create a framebuffer object
    glGenFramebuffers(1, &fboId);
    glBindFramebuffer(GL_FRAMEBUFFER, fboId);

    GLuint renderBuffer;
    glGenRenderbuffers(1, &renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);

    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, renderBufferWidth, renderBufferHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);

    GLuint depthRenderbuffer;
    glGenRenderbuffers(1, &depthRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, renderBufferWidth, renderBufferHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);

    // Step 10 - check FBO status
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status != GL_FRAMEBUFFER_COMPLETE) 
    {
        printf("Problem with OpenGL framebuffer after specifying color render buffer: \n%x\n", status);
    } 
    else 
    {
        printf("FBO creation succedded\n");
    }

    int size = 4 * renderBufferHeight * renderBufferWidth;
    unsigned char *data2 = new unsigned char[size];
    // Step 11 - clear the screen in Red and read it back
    glClearColor(1.0,0.0,0.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    eglSwapBuffers( eglDisplay, eglSurface);
    glReadPixels(0,0,renderBufferWidth,renderBufferHeight,GL_RGBA, GL_UNSIGNED_BYTE, data2);

   ... save data2 to image ...
}

但是如果我删除了fbo,并尝试直接绘制到pbuffer,我会在调用glClear()函数后立即看到分段错误:

#include <GLES2/gl2.h>
#include <EGL/egl.h>

int main(int argc, char *argv[])
{
    EGLint ai32ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2,
                                                     EGL_NONE };
    // Step 1 - Get the default display.
    EGLDisplay eglDisplay = eglGetDisplay((EGLNativeDisplayType)0);
    // Step 2 - Initialize EGL.
    eglInitialize(eglDisplay, 0, 0);
    // Step 3 - Make OpenGL ES the current API.
    eglBindAPI(EGL_OPENGL_ES_API);
    // Step 4 - Specify the required configuration attributes.
    EGLint pi32ConfigAttribs[5];
    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_WINDOW_BIT;
    pi32ConfigAttribs[2] = EGL_RENDERABLE_TYPE;
    pi32ConfigAttribs[3] = EGL_OPENGL_ES2_BIT;
    pi32ConfigAttribs[4] = EGL_NONE;
    // Step 5 - Find a config that matches all requirements.
    int iConfigs;
    EGLConfig eglConfig;
    eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs);

    if (iConfigs != 1) 
    {
        printf("Error: eglChooseConfig(): config not found.\n");
        exit(-1);
    }

     // Step 6 - Create a surface to draw to.
     EGLSurface eglSurface;
     eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, NULL);

     // Step 7 - Create a context.
     EGLContext eglContext;
     eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs);

     // Step 8 - Bind the context to the current thread
     eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

     int size = 4 * renderBufferHeight * renderBufferWidth;
     unsigned char *data2 = new unsigned char[size];
     // Step 11 - clear the screen in Red and read it back
     glClearColor(1.0,0.0,0.0,1.0);
     glClear(GL_COLOR_BUFFER_BIT);
     eglSwapBuffers(    eglDisplay, eglSurface);
     glReadPixels(0,0,renderBufferWidth,renderBufferHeight,GL_RGBA, GL_UNSIGNED_BYTE, data2);

     ... save data2 to image ...
}

我的环境是带有intel graphics / mesa的ubuntu 14。

你知道为什么我看到了分段错误吗? (我检查了eglcontext,这似乎是成功创建的。)你能确认pbuffer表面需要fbo吗?

编辑:正如Reto所指出的,我的问题是因为缺少属性。

设置这些属性后,我能够使用opengl es 2上下文。但是,我仍然遇到桌面opengl上下文的问题。

使用桌面opengl上下文,我只能获得透明图像,而不是回读红色图像。这是我目前的代码:

#include <QCoreApplication>
#include <QDebug>
#include <QImage>
#include <GL/gl.h>
#include <EGL/egl.h>
#include <QElapsedTimer>

int main(int argc, char *argv[])
{
    // Step 1 - Get the default display.
    EGLDisplay eglDisplay = eglGetDisplay((EGLNativeDisplayType)0);

    // Step 2 - Initialize EGL.
    eglInitialize(eglDisplay, 0, 0);

    // Step 3 - Make OpenGL ES the current API.
    eglBindAPI(EGL_OPENGL_API);

    // Step 4 - Specify the required configuration attributes.
    EGLint pi32ConfigAttribs[5];
    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_PBUFFER_BIT;
    pi32ConfigAttribs[2] = EGL_RENDERABLE_TYPE;
    pi32ConfigAttribs[3] = EGL_OPENGL_BIT;
    pi32ConfigAttribs[4] = EGL_CONFORMANT;
    pi32ConfigAttribs[5] = EGL_OPENGL_BIT;
    pi32ConfigAttribs[6] = EGL_COLOR_BUFFER_TYPE;
    pi32ConfigAttribs[7] = EGL_RGB_BUFFER;
    pi32ConfigAttribs[8] = EGL_LUMINANCE_SIZE;
    pi32ConfigAttribs[9] = 0;
    pi32ConfigAttribs[10] = EGL_RED_SIZE;
    pi32ConfigAttribs[11] = 8;
    pi32ConfigAttribs[12] = EGL_GREEN_SIZE;
    pi32ConfigAttribs[13] = 8;
    pi32ConfigAttribs[14] = EGL_BLUE_SIZE;
    pi32ConfigAttribs[15] = 8;
    pi32ConfigAttribs[16] = EGL_ALPHA_SIZE;
    pi32ConfigAttribs[17] = 8;
    pi32ConfigAttribs[18] = EGL_DEPTH_SIZE;
    pi32ConfigAttribs[19] = 8;
    pi32ConfigAttribs[20] = EGL_LEVEL;
    pi32ConfigAttribs[21] = 0;
    pi32ConfigAttribs[22] = EGL_BUFFER_SIZE;
    pi32ConfigAttribs[23] = 24;
    pi32ConfigAttribs[24] = EGL_NONE;
    // Step 5 - Find a config that matches all requirements.
    int iConfigs;
    EGLConfig eglConfig;
    eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs);
    qDebug() << "egl error" << eglGetError();

    if (iConfigs != 1)
    {
        printf("Error: eglChooseConfig(): config not found.\n");
        exit(-1);
    }

    EGLint pbufferAttribs[5];
    pbufferAttribs[0] = EGL_WIDTH;
    pbufferAttribs[1] = 1920;
    pbufferAttribs[2] = EGL_HEIGHT;
    pbufferAttribs[3] = 1080;
    pbufferAttribs[4] = EGL_NONE;

    // Step 6 - Create a surface to draw to.
    EGLSurface eglSurface;
    eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, pbufferAttribs);
    qDebug() << "egl error" << eglGetError();

    if (eglSurface == EGL_NO_SURFACE)
    {
        qDebug() << "surface issue";
    }

    // Step 7 - Create a context.
    EGLContext eglContext;
    eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, NULL);
    qDebug() << "egl error" << eglGetError();

    if (eglContext == EGL_NO_CONTEXT)
    {
         qDebug() << "context issue";
    }

    // Step 8 - Bind the context to the current thread
    bool result = eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

    if (!result)
    {
        qDebug() << "make current error" << eglGetError();
    }
    qDebug() << "egl error" << eglGetError();

    GLuint renderBufferWidth = 1920;
    GLuint renderBufferHeight = 1080;

    QElapsedTimer benchmarkTimer;
    int size = 4 * renderBufferHeight * renderBufferWidth;
    unsigned char *data2 = new unsigned char[size];
    int i = 0;
    benchmarkTimer.start();
    while(i<1000)
    {
        glClearColor(1.0,0.0,0.0,1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        eglSwapBuffers( eglDisplay, eglSurface);
        glReadPixels(0,0,renderBufferWidth,renderBufferHeight,GL_RGBA, GL_UNSIGNED_BYTE, data2);
        ++i;
    }
    qDebug() << "fps" << 1000.0*1000.0/benchmarkTimer.elapsed();

    QImage saveImage(data2, renderBufferWidth, renderBufferHeight, QImage::Format_RGBA8888_Premultiplied);
    saveImage.save("haha.png");
    QCoreApplication a(argc, argv);
    qDebug() << "done";
    return a.exec();
}

2 个答案:

答案 0 :(得分:3)

此代码中存在一些问题:

  1. 配置属性中指定的EGL_SURFACE_TYPE错误:

    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_WINDOW_BIT;
    

    要渲染到PBuffer,需要使用匹配值:

    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_PBUFFER_BIT;
    
  2. 没有为PBuffer指定大小。虽然man page表明在不指定大小的情况下创建PBuffer是合法的,但宽度和高度的默认值为0.我无法想象在尝试渲染时会发生任何好事表面大小为0倍0.要指定大小:

    EGLint pbufferAttribs[5];
    pbufferAttribs[0] = EGL_WIDTH;
    pbufferAttribs[1] = DesiredWidthOfPBuffer;
    pbufferAttribs[2] = EGL_HEIGHT;
    pbufferAttribs[3] = DesiredHeightOfPBuffer;
    pbufferAttribs[4] = EGL_NONE;
    eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, pbufferAttribs);
    
  3. 我对这个问题的回答有完整的代码,可以为两个不同版本的EGL创建一个上下文和PBuffer表面:GLES10.glGetIntegerv returns 0 in Lollipop only。代码使用Java绑定,但应该很容易适应。

答案 1 :(得分:0)

为什么你在glReadPixels之前做eglSwapBuffers? 据推测,eglSwapBuffers对PBufferSurface没有影响(因为它不是双缓冲区表面),但如果它是你会尝试从未定义的缓冲区读取像素,结果不确定..