RenderingPipeline

from geometry to pixels

Windowless OpenGL

OpenGL without a window can be useful for a couple of things: Console based OpenGL capability queries, (remote) rendering of movies, automated testing etc. OpenGL itself does not define how a context gets created, this functionality is provided by various window toolkits which normally don’t support the creation of a OpenGL context without a window.

On Linux it is actually possible to get a console application working with OpenGL as long as X is running. For that, you have to write some lines of GLX code to create a context on your own but don’t worry, that’s not too complicated: First, we will fetch the function pointers of the GLX functions needed. Don’t forget to test if those are NULL after the glXGetProcAddressARB call!

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
typedef Bool (*glXMakeContextCurrentARBProc)(Display*, GLXDrawable, GLXDrawable, GLXContext);
static glXCreateContextAttribsARBProc glXCreateContextAttribsARB = NULL;
static glXMakeContextCurrentARBProc   glXMakeContextCurrentARB   = NULL;
 
...
 
glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" );
glXMakeContextCurrentARB   = (glXMakeContextCurrentARBProc)   glXGetProcAddressARB( (const GLubyte *) "glXMakeContextCurrent"      );

Next we have to open a connection to the display. Here we see why a XSession has to run for this code to work, note that this will not open a window on that display! If the displayName parameter is NULL, XOpenDisplay will use the value stored in the environment variable $DISPLAY instead. If you ssh into a remote machine, you will have to modify it if you don’t want to be stuck at OpenGL 2.1.

const char *displayName = NULL;
Display* display = XOpenDisplay( displayName );

If this fails, display will be NULL.

Now we have to define the requirements of the framebuffer we need and query all possible configuration that match our requirements (e.g. certain color depth, multisampling etc.). As I will only use my own framebuffer objects anyway, I don’t really care what I can get.

static int visualAttribs[] = { None };
int numberOfFramebufferConfigurations = 0;
GLXFBConfig* fbConfigs = glXChooseFBConfig( display, DefaultScreen(display), visualAttribs, &numberOfFramebufferConfigurations );

If no configurations are available which meet your requirements, fbConfigs will be NULL.

The next block is more interesting, we get (hopefully) our OpenGL context. I explicitly request OpenGL 4.2 core profile with the ARB_debug_extension. Again, the context will be NULL if this config is not available.

int context_attribs[] = {
    GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
    GLX_CONTEXT_MINOR_VERSION_ARB, 2,
    GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB,
    GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
    None
};
 
GLXContext openGLContext = glXCreateContextAttribsARB( display, fbConfigs[0], 0, True, context_attribs);

The last thing to do is making the context current to start rendering. For this 99.9% of all applications would bind it to a window surface, but that is exactly not what we want. So we try to bind it to a pbuffer. As I already said, I will only work on FBOs and ignore framebuffer 0 anyway, so I just create a tiny framebuffer, but you might use it like a normal window surface if you like.

int pbufferAttribs[] = {
    GLX_PBUFFER_WIDTH,  32,
    GLX_PBUFFER_HEIGHT, 32,
    None
};
GLXPbuffer pbuffer = glXCreatePbuffer( display, fbConfigs[0], pbufferAttribs );
 
// clean up:
XFree( fbConfigs );
XSync( display, False );
 
if ( !glXMakeContextCurrent( display, pbuffer, pbuffer, openGLContext ) )
{
    // something went wrong
}

If anyone knows how to do something similar on MacOS X or Windows, please leave a comment below. Update: An idea for windows is posted below (thanks), windowless OpenGL on MacOS X has its own post.

, , , ,

15 thoughts on “Windowless OpenGL
  • Martins Mozeiko says:

    On Windows you must have HDC of HWND to get hardware accelerated OpenGL context. But you can simply not show Window (don’t call ShowWindow and don’t pass WS_VISIBLE as style for CreateWindow).

    You can get HDC of HBITMAP – bitmap in memory (instead of window). But that won’t give you hardware accelerated context.

    • Robert says:

      If you build a window that you don’t show, you shouldn’t render to the framebuffer 0 but instead use framebuffer objects as you might fail the pixel ownership test on some machines.

  • toto says:

    Hi,

    I have tried the code and it works great in graphic mode, but when I am in console mode it simply pauses on this call : “glXChooseFBConfig”. Does anyone knows where it comes from?

    Thx

    • Robert says:

      You need to have the X server running and need to be logged in. Switching to a console on that machine might break the possibility to run GL core from there even if the DISPLAY variable is set correctly. Connecting via SSH however should work. You can try to start your app on the console with “sleep 10; ./app” to wait 10 seconds before the app gets started and switch directly back to X (e.g. ALT+F7), so your X is displayed and ‘active’ when your app starts from your console. That should at least work.

  • toto says:

    A precision I am using ubuntu and opengl 3.

    • Gordon Bartels says:

      Did you get a valid framebuffer initialized ?

      I also use linux (ubuntu) with nvidia graphics driver, the code runs, but when
      I try to get a framebuffer object I always get “invalid framebuffer” error. Do you
      have a sample snipplet that successfully initializes an offscreen fb ?

      My code runs successfully on osmesa and ios and macos using GLES 2.0 and OpenGL 3.2. It just keeps failing on Linux …

      Any help would be appreciated.

      Thanks,

      Gordon

      PS: I use:

      glGenFramebuffers(1, &m_framebuffer1);
      glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer1);

      glGenRenderbuffers(1, &m_colorRenderbuffer1);
      glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer1);
      glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRenderbuffer1);

      glGenRenderbuffers(1, &m_depthRenderbuffer);
      glBindRenderbuffer(GL_RENDERBUFFER, m_depthRenderbuffer);
      glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthRenderbuffer);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthRenderbuffer);

      if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
      cout << "Failed to make complete framebuffer object " << glCheckFramebufferStatus(GL_FRAMEBUFFER) << endl;

      • Robert says:

        I tested it under Debian with the closed source nVidia drivers and I got a valid framebuffer and could in fact render everything in the same way as if I would have done it onscreen. I rendered to textures instead of renderbuffers tho. Does your code create correct FBOs on your setup when running the code in a windowed application?

        • Gordon Bartels says:

          Thanks for the fast reply.

          Yes I tried using a windowed approach, but get the same error.

          It seems that somehow my framebuffer config is not accepted by the driver. I boiled the essential parts of the init down to one small testprog (mostly using your code ). Could you run that in your environment and give me feedback, whether the framebuffer is correctly initialized ?

          The code is:

          #include
          #include
          #include
          #include
          #include
          #include

          typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
          typedef Bool (*glXMakeContextCurrentARBProc)(Display*, GLXDrawable, GLXDrawable, GLXContext);
          static glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
          static glXMakeContextCurrentARBProc glXMakeContextCurrentARB = 0;

          int pbuffer_width = 32;
          int pbuffer_height = 32;
          int framebuffer_width = 1024;
          int framebuffer_height = 768;

          int main(int argc, const char* argv[]){

          static int visual_attribs[] = {
          GLX_RENDER_TYPE, GLX_RGBA_BIT,
          GLX_RED_SIZE, 8,
          GLX_GREEN_SIZE, 8,
          GLX_BLUE_SIZE, 8,
          GLX_ALPHA_SIZE, 8,
          GLX_DEPTH_SIZE, 24,
          GLX_STENCIL_SIZE, 8,
          None
          };

          int context_attribs[] = {
          GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
          GLX_CONTEXT_MINOR_VERSION_ARB, 2,
          None
          };

          Display* dpy = XOpenDisplay(0);
          int fbcount = 0;
          GLXFBConfig* fbc = NULL;
          GLXContext ctx;
          GLXPbuffer pbuf;

          /* open display */
          if ( ! (dpy = XOpenDisplay(0)) ){
          fprintf(stderr, “Failed to open display\n”);
          exit(1);
          }

          /* get framebuffer configs, any is usable (might want to add proper attribs) */
          if ( !(fbc = glXChooseFBConfig(dpy, DefaultScreen(dpy), visual_attribs, &fbcount) ) ){
          fprintf(stderr, “Failed to get FBConfig\n”);
          exit(1);
          }

          /* get the required extensions */
          glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB( (const GLubyte *) “glXCreateContextAttribsARB”);
          glXMakeContextCurrentARB = (glXMakeContextCurrentARBProc)glXGetProcAddressARB( (const GLubyte *) “glXMakeContextCurrent”);
          if ( !(glXCreateContextAttribsARB && glXMakeContextCurrentARB) ){
          fprintf(stderr, “missing support for GLX_ARB_create_context\n”);
          XFree(fbc);
          exit(1);
          }

          /* create a context using glXCreateContextAttribsARB */
          if ( !( ctx = glXCreateContextAttribsARB(dpy, fbc[0], 0, True, context_attribs)) ){
          fprintf(stderr, “Failed to create opengl context\n”);
          XFree(fbc);
          exit(1);
          }

          /* create temporary pbuffer */
          int pbuffer_attribs[] = {
          GLX_PBUFFER_WIDTH, pbuffer_width,
          GLX_PBUFFER_HEIGHT, pbuffer_height,
          None
          };
          pbuf = glXCreatePbuffer(dpy, fbc[0], pbuffer_attribs);

          XFree(fbc);
          XSync(dpy, False);

          /* try to make it the current context */
          if ( !glXMakeContextCurrent(dpy, pbuf, pbuf, ctx) ){
          /* some drivers does not support context without default framebuffer, so fallback on
          * using the default window.
          */
          if ( !glXMakeContextCurrent(dpy, DefaultRootWindow(dpy), DefaultRootWindow(dpy), ctx) ){
          fprintf(stderr, “failed to make current\n”);
          exit(1);
          }

          } else {

          printf(“Init completed.”);

          }

          printf(“Vendor: %s\n”, (const char*)glGetString(GL_VENDOR));

          int m_framebuffer1;
          int m_colorRenderbuffer1;
          int m_depthRenderbuffer;

          glGenFramebuffers(1, &m_framebuffer1);
          glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer1);

          glGenRenderbuffers(1, &m_colorRenderbuffer1);
          glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer1);
          glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, framebuffer_width, framebuffer_height);
          glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRenderbuffer1);

          glGenRenderbuffers(1, &m_depthRenderbuffer);
          glBindRenderbuffer(GL_RENDERBUFFER, m_depthRenderbuffer);
          glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, framebuffer_width, framebuffer_height);
          glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthRenderbuffer);
          glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthRenderbuffer);

          if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
          printf(“Failed to make complete framebuffer object %x\n”,glCheckFramebufferStatus(GL_FRAMEBUFFER));
          else
          printf(“Success, finally did it!”);

          return 0;
          }

          Compiled using:

          gcc testConfig.c -o testConfig -lGL

          Running:

          # ./testConfig
          Init completed.Vendor: NVIDIA Corporation
          Failed to make complete framebuffer object 8cdd
          #

          Thanks for your help and the effort !

          • Robert says:

            Hello Gordon,

            it works on my system but I made two changes: I had to add GLEW to get the function pointers of OpenGL. After doing this the compiler showed me a problem with your m_framebuffer1 / m_colorRenderbuffer1 / m_depthRenderbuffer handles: they should be GLuint which is a unsigned integer instead of a (signed) int. After changing this I got a 3.2 context and the FBO creation worked (closed source NVidia drivers 304.48 on Debian).

        • Gordon Bartels says:

          Sorry for the includes above. They got mangled as HTML Tags.
          The files are:

          #include stdio.h
          #include stdlib.h
          #include X11/X.h
          #include X11/Xlib.h
          #include GL/gl.h
          #include GL/glx.h

  • Stefan says:

    Robert and Gordon, I’m using ubuntu 12.04 and opengl 4.2. I tried to run the program, Robert posted above (with the changes Gordon suggested). However, I get a segfault when it comes to

    glGenFramebuffers(1, &m_framebuffer1);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer1);

    Everything before these two lines passes smoothly. Any idea what could cause this segfault?

    Thank you,
    Stefan

    • Robert says:

      Have you loaded your OpenGL function pointers (GLEW, glLoadGen etc)? glGenFramebuffer might be present in the gl header but not loaded at runtime without you doing it explicitly.

      • Stefan says:

        What I have done differently from your example above is, that I have called

        glewInit();

        before

        GLuint m_framebuffer1;
        GLuint m_colorRenderbuffer1;
        GLuint m_depthRenderbuffer;

        Anything else I need to call?
        Thank you,
        Stefan

  • Enhao Gong says:

    I have successfully followed all the code and created context and pbuffer. I setup FBO, shaders, programs, etc and all seems to work. However there is a Segmentation fault when I call glutSwapBuffers() in render(). Do I need to do anything else in order to conduct rendering?

    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *

*