Skip to content

Instantly share code, notes, and snippets.

@andyneff
Created December 29, 2017 16:54
Show Gist options
  • Save andyneff/36293b1aeb509fd1c6313afabac777ee to your computer and use it in GitHub Desktop.
Save andyneff/36293b1aeb509fd1c6313afabac777ee to your computer and use it in GitHub Desktop.
EGL Notes.md

Gotchas

  • Nvidia Driver 375.39 on ubuntu is broken when it comes to EGL. Follow comment #11 to fix the host machine.

    • To fix an nvidia-docker driver mount, you need to either fix /var/lib/nvidia-docker/volumes/nvidia_driver/375.39/lib64 or use a docker via docker run -it --rm -v nvidia_driver_375.39:/nvidia_driver debian to gain write permissions to the drivers, and patch them.
      • Copy /usr/lib/nvidia-375/libEGL.so.375.39 into the {nvidia_driver}/lib64/ directory
      • Make sure the symlinks for {nvidia_driver}/lib64/libEGL.so.1 and {nvidia_driver}/lib64/libEGL.so eventually link to the libEGL.so.375.39 file.
  • If you have the environment variable DISPLAY set, EGL will try and use the actual Display for offscreen OpenGL rendering instead of the headless route. This is difficult (but not impossible) because now you must run the command as the same user as the graphically logged in user. Plus this means you are using the DISPLAY graphics card, not one of the others for rendering. This should be avoided. Either unset DISPLAY in the shell or in C:

    unsetenv("DISPLAY"); //Force Headless
    eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  • I have to call glewInit(); before making any GLEW calls

Requirements

Development

  • libgl1-mesa-dev
  • libegl1-mesa-dev
  • libglew-dev (optional if you use glew, this example does for FBO)

Running (untested)

  • libglew (optional if you use glew, this example does)
  • Vendor OpenGL and EGL
  • libglew

1. Getting a "display"

To get a specific headless display (called a platform), EGL extensions are needed. I would rather fail back to using the default eglGetDisplay command if anything goes wrong.

EGLDisplay eglGetDisplay_(NativeDisplayType nativeDisplay=EGL_DEFAULT_DISPLAY)
{
  EGLDisplay eglDisplay = eglGetDisplay(nativeDisplay);
  checkEglError("Failed to Get Display: eglGetDisplay");
  std::cerr << "Failback to eglGetDisplay" << std::endl;
  return eglDisplay;
}

To redundantly getting a display platform:

PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
checkEglError("Failed to get EGLEXT: eglQueryDevicesEXT");
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
checkEglError("Failed to get EGLEXT: eglGetPlatformDisplayEXT");
PFNEGLQUERYDEVICEATTRIBEXTPROC eglQueryDeviceAttribEXT = (PFNEGLQUERYDEVICEATTRIBEXTPROC)eglGetProcAddress("eglQueryDeviceAttribEXT");
checkEglError("Failed to get EGLEXT: eglQueryDeviceAttribEXT");

if (cudaIndexDesired >= 0)
  {
    EGLDeviceEXT *eglDevs;
    EGLint numberDevices;

    //Get number of devices
    checkEglReturn(
      eglQueryDevicesEXT(0, NULL, &numberDevices),
      "Failed to get number of devices. Bad parameter suspected"
    );
    checkEglError("Error getting number of devices: eglQueryDevicesEXT");
    
    std::cerr << numberDevices << " devices found" << std::endl;

    if (numberDevices)
    {
      EGLAttrib cudaIndex;
    
      //Get devices
      eglDevs = new EGLDeviceEXT[numberDevices];
      checkEglReturn(
        eglQueryDevicesEXT(numberDevices, eglDevs, &numberDevices),
        "Failed to get devices. Bad parameter suspected"
      );
      checkEglError("Error getting number of devices: eglQueryDevicesEXT");

      for(i=0; i<numberDevices; i++)
      {
        checkEglReturn(
          eglQueryDeviceAttribEXT(eglDevs[i], EGL_CUDA_DEVICE_NV, &cudaIndex),
          "Failed to get EGL_CUDA_DEVICE_NV attribute for device"
        );
        checkEglError("Error retreiving EGL_CUDA_DEVICE_NV attribute for device");

        if (cudaIndex == cudaIndexDesired)
          break;
      }
      if (i < numberDevices)
      {
        eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, eglDevs[i], 0);
        checkEglError("Error getting Platform Display: eglGetPlatformDisplayEXT");
        std::cerr << "Got Cuda device " << cudaIndex << std::endl;
      }
      else
      {
        eglDisplay = eglGetDisplay_();
      }
    }
    else
    {//If no devices were found, or a matching cuda not found, get a Display the normal way
      eglDisplay = eglGetDisplay_();
    }
  }
  else
  {
    eglDisplay = eglGetDisplay_();
  }

  if (eglDisplay == EGL_NO_DISPLAY)
    throw EGLException("No Disply Found");
  //printf("Display %lu used\n",*(uint64_t*)eglDisplay); Points, always says 0

2. Initialize a connection to the "display"

eglInitialize(eglDisplay, &major, &minor);
checkEglError("Failed to initial display: eglInitialize");
std::cerr << "Display initialized for EGL " << major << "." << minor << std::endl;

3. Select an appropriate configuration

There are usually many connections that match a given filter. EGL tries to sort them in a decent order. In my tests this worked out, but sometimes it is not ideal and will waste resources.

EGLint numberConfigs;
EGLConfig eglConfig;
eglChooseConfig(eglDisplay, configAttribs, &eglConfig, 1, &numberConfigs);

4. Binding an API

You have to bind one of the available APIs to your thread. Your choices are OpenGL, OpenGL ES, or OpenVG

eglBindAPI(EGL_OPENGL_API);
checkEglError("Failed to bind OpenGL API: eglBindAPI");

5. Create Context

Now you are ready to create your context

EGLContext eglCtx = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, NULL);
checkEglError("Failed to create context: eglCreateContext");

6. Selecting your Context

Now when you are ready to draw/read/use your context, make it current every time

eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx);
checkEglError("Failed to make context current: eglMakeCurrent");

Since we usually want to use FBOs, there's no point in sending a dummy surface

7. Profit!!!

Well, from here on, do what ever normal GL commands you usually do. Such as creating an FBO

GLuint color_tex, depth_tex;

  glGenTextures(1, &color_tex);
  glBindTexture(GL_TEXTURE_2D, color_tex);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  //NULL means reserve texture memory, but texels are undefined
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 300, 300, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);

  glGenTextures(1, &depth_tex);
  glBindTexture(GL_TEXTURE_2D, depth_tex);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
  //NULL means reserve texture memory, but texels are undefined
  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 300, 300, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

  GLuint fb;
  glewInit();
  glGenFramebuffers(1, &fb);
  glBindFramebuffer(GL_FRAMEBUFFER, fb);
  //Attach 2D texture to this FBO
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_tex, 0);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0);

  glDrawBuffer(GL_COLOR_ATTACHMENT0);

And set up a viewpoint, and clear the buffer

glViewport(0, 0, (GLint) winWidth, (GLint) winHeight);

glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

And for a sanity check, read the buffer and write it to a raw text file

char* my_buffer = (char*)malloc(winWidth*winHeight*3);
FILE* my_file;
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0,0,winWidth, winHeight, GL_RGB, GL_UNSIGNED_BYTE, my_buffer);
my_file = fopen("test.bin", "wb");
fwrite(my_buffer, 3, winWidth*winHeight, my_file);
fclose(my_file);
free(my_buffer);

And read it in with some easy to use python (notebook)

%matplotlib notebook
from PIL import Image
import numpy as np
data = np.fromfile('test.bin', dtype=np.uint8)
data = data.reshape(300, 300, 3)
data = np.flipud(data)
img = Image.fromarray(data)
img

Exception handling

The error handling functions used above are:

#include <exception>

class EGLException:public std::exception
{
  public:
    const char* message;
    EGLException(const char* mmessage): message(mmessage) {}

    virtual const char* what() const throw()
    {
      return this->message;
    }
};

class EGLReturnException: private EGLException
{
  using EGLException::EGLException;
};

class EGLErrorException: private EGLException
{
  using EGLException::EGLException;
};

#define checkEglError(message){ \
    EGLint err = eglGetError(); \
    if (err != EGL_SUCCESS) \
    { \
        std::cerr << "EGL Error " << std::hex << err << std::dec << " on line " <<  __LINE__ << std::endl; \
        throw EGLErrorException(message); \
    } \
}

#define checkEglReturn(x, message){ \
    if (x != EGL_TRUE) \
    { \
        std::cerr << "EGL returned not true on line " << __LINE__ << std::endl; \
        throw EGLReturnException(message); \
    } \
}
@henzler
Copy link

henzler commented Jun 10, 2018

Why do you install: libgl1-mesa-dev and libegl1-mesa-dev? I thought if you wanted nvidia acceleration you should avoid using mesa stuff?

@Neo-X
Copy link

Neo-X commented Jul 4, 2018

Those libraries will include the openGL ES header files needed for this code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment