July 19, 2015

DerelictGLES 0.0.1

Quite a while back, Ben Boeckel put together a binding for OpenGLES 2. It’s been sitting in the DerelictOrg group ever since, but hasn’t been in the DUB registry. Also, it only had support for loading on Linux.

Recently, when I had some downtime from the book, I decided to spend it trying to get Google’s Angle project working with DerelictGLES. Toward that end, I added some missing support for Windows, including the loading of libegl.dll and libglesv2.dll. Then I decided to try to get it working with SDL.

Long story short, when creating a window with a GLES context, SDL tries first to load everything via the graphics driver and will only load Angle if that fails. The only way to force it to use Angle is to create the EGL context yourself, then pass the window handle to SDL_CreateWindowFrom. I haven’t gone that route yet. Instead, I wanted to get it working with SDL’s context creation.

The problem is that DerelictGL.load will only load the Angle shared library, so if SDL is using the driver’s GLES2 implementation, then there are two different implementations loaded in memory and nothing is going to work. Since there’s no way to force SDL to use Angle, and since the drivers do not have a common name for their GLES DLLs, I opted to add a new load method to DerelictGLES that takes a pointer to a symbol loading function. An implementation for SDL looks like this:

void* loadSymbol(string name) {
    import derelict.util.exception : SymbolLoadException;
    import std.format : format;

    auto sym = SDL_GL_GetProcAddress(name.ptr);
    if(!sym) throw new SymbolLoadException(format("Failed to load symbol %s: %s",
        to!string(SDL_GetError())));
    return sym;
}

With that, using DerelictGLES with SDL looks like this:

DerelictSDL2.load();
if(SDL_Init(SDL_INIT_VIDEO) == -1) throw new Error("Failed to init SDL: " ~
    to!string(SDL_GetError()));

// Configure SDL to use OpenGLES2.
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);

// This line is optional. If you have Angle (libegl.dll and libglesv2.dll)
// on the library path, SDL will try to load it if the driver does not support
// GLES2. When they are the same binaries that ship with Chrome, Angle requires the
// that d3dcompiler_47.dll be preloaded. With custom-built Angle binaries, this
// requirement can be turned off. If you do want to use Angle as a fallback, this
// needs to be called before creating the window.
SDL_SetHint(SDL_HINT_VIDEO_WIN_D3DCOMPILER, "d3dcompiler_47.dll");

window = SDL_CreateWindow("Hello Triangle",
    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
    winWidth, winHeight, SDL_WINDOW_OPENGL);
if(!window) throw new Error("Failed to create window: " ~
    to!string(SDL_GetError()));

context = SDL_GL_CreateContext(window);
if(!context) throw new Error("Failed to create GLES context: " ~
    to!string(SDL_GetError()));
SDL_GL_MakeCurrent(window, context);

DerelictGLES2.load(&loadSymbol);
writeln(to!string(glGetString(GL_VENDOR)));
writeln(to!string(glGetString(GL_VERSION)));

Using this approach, it’s important that DerelictGLES.load be called *after* creating the window and the context. The benefit is that no matter which GLES2 implementation SDL has loaded, the binding will be tapped in to it. I have tested this using the Hello Triangle example from the OpenGL ES 2.0 Programming Guide on Windows. It compiles and runs fine, reporting that it’s using NVidia’s implementation.

This approach to loading should also work with GLFW. It supports GLES already and should have Angle support in version 3.2.

If you’re interested in using this, please give it a spin yourself and see if it breaks for you. Currently, only Linux and Windows are supported. I have a Mac and have no idea what GLES support is like on OSX, so there’s no support for it whatsoever at the moment. I’ve seen some Mac stuff in the Angle headers, though, but I have no idea what that’s about.