pbuffer vs fbo in egl offscreen rendering

10,125

There are a couple of problems in this code:

  1. The EGL_SURFACE_TYPE specified in the config attributes is wrong:

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

    To render to a PBuffer, this needs to use the matching value:

    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_PBUFFER_BIT;
    
  2. No size is specified for the PBuffer. While the man page suggests that it's legal to create a PBuffer without specifying a size, the defaults for width and height are 0. I can't imagine that anything good will happen when trying to render to a surface of size 0 times 0. To specify a size:

    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);
    

My answer to this question has complete code to create a context and PBuffer surface for two different versions of EGL: GLES10.glGetIntegerv returns 0 in Lollipop only. The code uses the Java bindings, but it should be easy to adapt.

Share:
10,125
Bill Yan
Author by

Bill Yan

Updated on June 04, 2022

Comments

  • Bill Yan
    Bill Yan about 2 years

    I'm very confused at egl pbuffer surface. In my opinion, a pbuffer surface is a platform independent surface, just like the windows surface or the pixmap surface. Things drawn to that surface, although not visible, should be able to be read back.

    the answer to this question seems to confirm my understanding:

    Difference from eglCreatePbufferSurface and eglCreatePixmapSurface with OpenGL ES(EGL)

    however, my experiment shows that I need to create a fbo buffer in addition to using pbuffer surface.

    this code seems to work for me, it creates a pbuffer surface and then a 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 ...
    }
    

    However if I remove the fbo, and try to draw to the pbuffer directly, I will see a segmentation fault immediately after calling glClear() function:

    #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 ...
    }
    

    My environment is ubuntu 14 with intel graphics/mesa.

    Do you know why I saw the segmentation fault? (I checked the eglcontext, which seems to be created successfully.) Can you confirm that fbo is needed with pbuffer surface?

    EDIT: as pointed out by Reto, my problem is because of missing attributes.

    after setting those attributes, I was able to make things work with opengl es 2 context. However, I still have issues with the desktop opengl context.

    Instead of reading back a red image, with the desktop opengl context, I could only get a transparent image. here is my current code:

    #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();
    }