Headless OpenGL Rendering using ssh and X11 forwarding

Please help,

I have been going around and around with this problem but cannot seem to make any headway. I hope that one of you OpenGL EGL experts can help. :slight_smile:

I have created a program that uses OpenGL EGL (version 1.5) with OpenGL 3 that successfully renders an offscreen triangle and saves it to an image file (PNG) when I ssh without X11 forwarding on my Linux (Ubuntu 22.04) machine.

However when I try the same thing using ssh with X11 forwarding enabled I get the following EGL error when I call eglInitialize(…): 12290 (I think is EGL_BAD_ACCESS).

This seems really weird and I hope it is something simple that I am just not currently seeing.

I really like using OpenGL with EGL but need a way to remedy this situation if possible. Is there a way for EGL to determine if X11 forwarding is being employed and to ignore it or some other solution?

The snippet of relevant C++ code follows, with area where error occurs marked:

#include <iostream>
#include <cstdlib>

#include <EGL/egl.h>

#include <EGL/eglext.h>
#include <GL/gl.h>
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display == EGL_NO_DISPLAY) {
    std::cerr << "Failed to get EGL display: " << eglGetError() << std::endl;
EGLint major;
EGLint minor;
if(eglInitialize(display, &major, &minor) == EGL_FALSE) {
    // ERROR 12290 is generated here
    std::cerr << "Failed to initialize EGL: " << eglGetError() << std::endl;

Any help would be greatly appreciated.

What is your $DISPLAY environment variable set to inside of the ssh shell session in each of these cases, before you run your application?

When using ssh’s X11 forwarding, $DISPLAY will be something like “:8”, i.e. it won’t have a hostname. X clients connect to the Unix-domain socket created by sshd, which forwards the data back across the ssh connection to the ssh client, which forwards it to the X server on the ssh client system.

At both ends, the X connection is local (Unix-domain socket), but OpenGL still has to use indirect rendering (so it isn’t going to support anything beyond 1.x).

There might be a way around this using environment variables; Mesa has the ability to invoke the Gallium (softpipe/llvmpipe) renderer on the client system and send the rendered image to a remote X server.

But OP is probably better off asking this sort of question on the Mesa mailing lists or IRC channel. Link.

Thank you @GClements and @Dark_Photon.

I will link to the Mesa forum with the question.

Just to be sure, there is no way for OpenGL EGL to ignore if X11 forwarding is on since it will be rendering to offscreen image file anyway?

Maybe check if X11 is available in the C++ code? Something like the following:

#include <X11/Xlib.h>

#ifdef __unix__
  #ifdef __linux__
    #include <X11/Xlib.h>

bool isX11Available()
#ifdef __unix__
  #ifdef __linux__
    Display *display = XOpenDisplay(NULL);
    if (display)
      return true;
      return false;
    return false;
  return false;

int main()
  if (isX11Available())
    std::cout << "X11 is available." << std::endl;
    std::cout << "X11 is not available." << std::endl;
    // Run the headless OpenGL EGL code in this branch ????

  return 0;

Even if you render to an image, EGL is going to try to get the video hardware to do the rendering. For the case of a relatively simple scene and a remote X server, that may be slower than doing it in software (because of copying the image over the network).

EGL wasn’t designed with X11 in mind, particularly not indirect rendering. It was designed for smartphones which run neither Windows (so no wgl* functions) nor X11 (so no glX* functions).

Thank you for the information. The fact that EGL wasn’t designed with X11 in mind makes a lot of sense. I’m not stuck on EGL and have not problem using just OpenGL 3.X (or greater).

If this is the case, can you recommend a good option for rendering offscreen images with OpenGL 3.X? Is it good to use something like glfw3 ? Currently I only need this to execute on Linux architectures.

Thank you again for all the assistance.

If you want to use the hardware for rendering, use a toolkit such as GLUT or GLFW. You’ll need to create a window, but the window shouldn’t need to be visible. You can use framebuffer objects for off-screen rendering (prior to 3.0, you would need to use pbuffers or GLX Pixmaps, which would require using Xlib+glX directly).

Otherwise, OSMesa can render to a block of memory without involving an X server.

1 Like

Thank you @GClements for the response. I will give the GLUT/GLFW a try, making the window not visible.

Is OSMesa a performance drag, in general?

Yes. I know how ssh X11 tunneling works. I was trying to establish which X server display/screen/protocol the OP was making use of 1) without and 2) with X11 tunnelling active (mostly the former). And to verify they don’t have any override “export DISPLAY=… / setenv DISPLAY …” settings in their shell startup scripts on the remote end (.profile, .cshrc, etc.) At this point, we still don’t know.

Sure there is. You can point it to whatever X display/screen/protocol you want. Even the same one. You’re currently using the default display. That’s provided by the $DISPLAY environment variable.

When logging in remotely via ssh with X11 tunneling active, ssh overrides the setting of $DISPLAY in your shell environment to point to a virtual X display (e.g. localhost:10.0), which tunnels X display protocol back the client through the net connection. You just want to force it to use the display you were using before, if you were otherwise satisfied with it. You didn’t answer my original question, so we still don’t know what that original setting was.

Related to your question, see:

Notice the mentions of the DISPLAY environment variable. Initing EGL with the default display invokes similar behavior.

Also note in the above what you can provide as an argument to XOpenDisplay() instead of NULL, if you don’t want it to use the default.

Similarly, notice what you can provide to eglGetDisplay() instead of EGL_DEFAULT_DISPLAY, if you don’t want it to use the default:

No, it’s not. It’s going to use whatever X display you point it to, implicitly or explicitly. This doesn’t inherently constrain the rendering method to be either GPU or CPU rendering, or to be performed on the local machine or a remote machine.

That said, it is possible to use EGL + OpenGL to do GPU hardware accelerated offscreen rendering with or without a connection to any X server, depending on the system config, and some cases independent of whether or not you’re logged in locally or remotely. But it’s not clear what the OP needs yet.

Really. Got a source for that? It’s not true.

From the spec:

There are sure a lot of X11 references in the EGL spec, … for it not being designed to work well with it. Heck, it even supports rendering to Pixmaps and PBuffers. Where do you think that came from?

Thank you @Dark_Photon for the response. You raise some interesting points.

The problem with this is I don’t honestly know as it wasn’t my particular machine that expressed the issue, it was another user. I was told that DISPLAY was undefined (don’t know if that helps though).

Sorry I wasn’t clearer with my question(s). I would like to be able to use EGL + OpenGL with GPU hardware accelerated offscreen rendering when logged in remotely.

Is it true that EGL is primarily used with Nvidia cards? I have seen a lot of references using Nvidia and EGL.

Ok. I guess we’ve got some conflicting information in this thread then. Here in your last post, it sounds like someone else ran the tests (successful test, unsuccessful test, or both). But here in your first post:

it sounds like you wrote the code and you ran the tests that: 1) succeeded and 2) failed.

I guess the first step is for you to reproduce the: 1) successful test and 2) unsuccessful test. Then you can evaluate the results.

If they jive with what you’ve heard, and they still don’t make sense to you, then you can post those test details and findings here with the exact EGL init code you’re using (if not identical to that you listed in your first post) and ask for feedback.

Ok, thanks for clarifying. So, your requirements are:

  1. Running on a remote Linux machine (logged in via ssh)
  2. Rendering
    a. using the GPU on that remote machine
    b. to an offscreen render target
    c. via EGL and OpenGL (or OpenGL ES?)

Generally speaking, there are two ways to do this that I know of (so there are probably others :slight_smile: ):

  1. Use EGL or GLX to connect to a display on the remote machine’s X Server
  2. Use EGL to connect to a specific GPU on the remote machine, bypassing the remote machine’s X server (if any)

Both require the remote machine to be configured to allow this type of access. Long gone are the days when you can just, by default, start loading up the GPU on a remote machine without permission.

#1 should work with any driver+GPU. You could use this method to run an EGL+OpenGL ES based app via ANGLE, which would translate to the native platform’s GLX + OpenGL under-the-hood (and render on that machine’s local GPU). Or, just use the EGL provided by the native graphics driver on the remote machine (NVIDIA’s driver at least has one) to connect, rendering on that local machine’s GPU via OpenGL or OpenGL ES. Alternatively, if you don’t care about EGL, just use GLX or some GLX-wrapper like GLUT or GLFW as the window system layer used to connect to the X server. Then use OpenGL or OpenGL ES underneath that as desired.

For #1, the first step is getting X display connect access from the admin. The admin for the remote machine will tell you how to connect to its X server, once they allow access for you. Then you just connect to one of its X displays using the method they provide. For instance, by setting DISPLAY=:0 before running your application, connecting to the default display. Then do your offscreen rendering on the GPU, however you want to do it (EGL+OpenGL ES, GLX+OpenGL, GLFW+OpenGL, etc.)

#2 OTOH requires an NVIDIA GPU+driver (AFAIK). For usage details, see:

There are details in the forum archives on how to allow access for GPU rendering using this method. Basically, it involves getting your user-id added to a user group that has permission to connect to the GPU via the graphics driver. Again, work with the admin on the remote machine to obtain access for this rendering method.

1 Like

Thank you @Dark_Photon for the detailed response, there is a lot of great information to dig though.

It is late, so maybe I’m missing something that I might more easily see in the morning :slight_smile: but for a fast solution I am interested in forcing EGL to use the display I was using before, as you mentioned in the following:

Can you tell me how to force EGL to do this? Some simple code sample if possible.

The other ideas I will definitely investigate further when time permits as they are very good ideas.

Thank you again for all the help

I could. If you’d done this:

But you haven’t told us:

  1. That you have personally reproduced the successful test (performed via “ssh without X11 forwarding”), when you (they?) were satisfied with the result, and
  2. What X display name you were connecting with at the time (e.g. specified by $DISPLAY)

Once you get that, I suspect this is going to be as simple as setting the $DISPLAY env var to that setting in your environment.

Of course, you could do this forcing in your EGL C++ code to, either by establishing that env var setting before doing the EGL init with the default display. Or by creating your own X display connection (e.g. via XOpenDisplay() with that display string setting, and then feeding that to EGL as the display to use. Obviously the former 2 options involve less code (0 lines and 1 lines, respectively).

Thanks for reply @Dark_Photon. Sorry about not providing the information to help solve the issue.

The test is successful when ssh without X11 is executed. Sorry but I can’t reproduce beyond that as the person that had this problem is away for a while :frowning:

I did try the following to force EGL to use default DISPLAY as follows:

int main() {
    const char* display = getenv("DISPLAY");
    if (!display) {
        std::cerr << "Error: DISPLAY environment variable not set." << std::endl;
        return 1;

    EGLDisplay egl_display = eglGetDisplay(display);
    if (egl_display == EGL_NO_DISPLAY) {
        std::cerr << "Error: eglGetDisplay failed." << std::endl;
        return 1;

    EGLint major, minor;
    if (!eglInitialize(egl_display, &major, &minor)) {
        std::cerr << "Error: eglInitialize failed." << std::endl;
        return 1;

    // create and configure EGL context here

    return 0;

However this fails when I execute it at the eglInitialize function call as an uninitialized EGL display. I then check my $DISPLAY environment variable as echo $DISPLAY. This returned an empty string, no value. So apparently my $DISPLAY variable is not set. I am trying to figure out where to set this in my Ubuntu 22.04, I could set it for a particular terminal session but I need this to be permanent if possible.

I did a lot of driver reinstalls on the system that was running the OpenGL EGL code and it looks like I had the wrong driver. My system is Ubuntu 22.04.

I removed the Nvidia driver that came with the Ubuntu distro and installed the one provided by Nvidia, I also installed libnvidia-egl-wayland library. Now the OpenGL rendering using EGL works when using ssh with X11 tunneling, which makes more sense. However, now the code fails at the eglInitialize using EGL_DEFAULT_DISPLAY in the eglGetDisplay when ssh is invoked without X11 tunneling.

When ssh without X11 tunneling and run echo $DISPLAY I get an empty string. So, now I’m not real sure which direction to go in as I would like render offscreen using EGL without the X server running.

I’ve already answered the question you posted above, on the following peer thread you’ve got going. So just read the answer there.

And despite the different thread title, you’ve morphed this thread into the exact same topic as the following thread:

…except there you’ve said you won’t assume an NVIDIA GPU.

So just read the rest of that thread and follow-up there. Let’s stop posting to this thread and continue that one.

Apologies about the repeats and morphing into the other question.

You are right, I should probably just continue on the other thread.