Using OpenGL ES on windows desktops via EGL

OpenGL_ES_logo

OpenGL ES is an embedded version of OpenGL mostly used on mobile devices that offer only limited GPU capabilities compared to modern desktop GPUS.

Introduction

Thanks to android, OpenGL ES is very widespread, and as WebGL is also based on OpenGL ES (2.0), chances are pretty high you’ll get into contact with OpenGL ES at some point during your 3D development efforts.

And at some point you may want to prototype your OpenGL ES applications on a desktop system (windows, linux, mac), or just distribute a desktop version of your mobile game using OpenGL ES.

Depending on the vendor of your desktop GPU, the way you access OpenGL on your desktop system differ though. While NVIDIA and INTEL expose OpenGL ES functionality via OpenGL using extensions ([1] [2] [3] [4]) similar to the ones for OpenGL core and compatibility contests, the situation for AMD (ATI) is very different, and that’s what this article is about.

It’s a starting point for getting OpenGL ES up and running with AMD GPUs on windows (adopting to linux should be pretty easy). If you’re on NVIDIA or INTEL, you can simply use a current glfw version and request an OpenGL ES context and you’re set.

EGL_OpenGL_Logo

AMD doesn’t expose OpenGL ES on desktops via OpenGL extensions (so you won’t find these on any AMD device), but goes the same route as Android devices and exposes OpenGL ES via EGL.

EGL is a native platform interface that connects the windowing systems to one of Khronos’ APIs, e.g. OpenGL ES.

So before getting to OpenGL ES on e.g. windows with AMD, you first have to setup EGL and an EGL render context using OpenGL ES as the client API.

Setup

Before you can start using EGL on your desktop system, you need to get library files and binaries.

You can get them from :

While the PowerVR SDK is the biggest download, it supports the widest range of OpenGL ES versions and operating systems. If you only want to do OpenGL ES 2 on windows, go and grab the AMD one. ANGLE is the OpenGL ES backend used in Google’s chrome and wraps the OpenGL ES calls (for WebGL) to DirectX.

You also need the EGL and OpenGL ES headers, which should be included in the SDK. The latest version can always be obtained via the Khronos API registry for EGL and OpenGL ES.

If you include the headers for EGL and OpenGL ES and tell the linker where to find the libraries, it’s time for some code.

You can find the whole C++ demo over at my github repository.

The EGL man pages can be accessed here.

Step 1 - Connecting to the “display”

The term display may sound a bit odd at first if you’re used to Windows, and more familiar if you’re coming from Linux.

A display, in EGL terms, on Windows is just a normal HDC. So simply get a device context from your window (or control you want to paint on) and connect it to the EGL display :

    // Create a new window for render output
    HWND hwnd = createWindow(winWidth, winHeight);
    // Get device context from the window
    HDC hdc = GetDC(hwnd);
    // Create EGL display connection
    EGLDisplay eglDisplay = eglGetDisplay(hdc);
    // Initialize EGL for this display, returns EGL version
    EGLint eglVersionMajor, eglVersionMinor;
    eglInitialize(eglDisplay, &eglVersionMajor, &eglVersionMinor);

Step 2 - Binding to the API

Now that we’ve got a valid EGL display, we need to select the client API we want to use. EGL supports OpenGL, OpenGL ES and OpenGL VG. Note that not all client APIs may be available on your device. We’re going to use OpenGL ES, the version will be selected later on :

	eglBindAPI(EGL_OPENGL_ES_API);

Step 3 - Selecting a valid config

Similar to finding a suitable pixel format for context creating in OpenGL, we need to find a valid EGL configuration that supports our requested client API and fits our description :

    EGLint configAttributes[] =
    {
    	EGL_BUFFER_SIZE, 0,
    	EGL_RED_SIZE, 5,
    	EGL_GREEN_SIZE, 6,
    	EGL_BLUE_SIZE, 5,
    	EGL_ALPHA_SIZE, 0,
    	EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
    	EGL_DEPTH_SIZE, 24,
    	EGL_LEVEL, 0,
    	EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    	EGL_SAMPLE_BUFFERS, 0,
    	EGL_SAMPLES, 0,
    	EGL_STENCIL_SIZE, 0,
    	EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
    	EGL_TRANSPARENT_TYPE, EGL_NONE,
    	EGL_TRANSPARENT_RED_VALUE, EGL_DONT_CARE,
    	EGL_TRANSPARENT_GREEN_VALUE, EGL_DONT_CARE,
    	EGL_TRANSPARENT_BLUE_VALUE, EGL_DONT_CARE,
    	EGL_CONFIG_CAVEAT, EGL_DONT_CARE,
    	EGL_CONFIG_ID, EGL_DONT_CARE,
    	EGL_MAX_SWAP_INTERVAL, EGL_DONT_CARE,
    	EGL_MIN_SWAP_INTERVAL, EGL_DONT_CARE,
    	EGL_NATIVE_RENDERABLE, EGL_DONT_CARE,
    	EGL_NATIVE_VISUAL_TYPE, EGL_DONT_CARE,
    	EGL_NONE
    };

We’ll be requesting a pretty basic config with 5 bits for red, 6 for green and 5 for blue and 24 bits of depths. Note the “EGL_RENDERABLE_TYPE” containing the OpenGL ES 2 bit we’re going to request a config for. Since OpenGL ES 3 is using the same base as OpenGL ES 2, while OpenGL ES and OpenGL ES 2 greatly differ, you’d use EGL_OPENGL_ES2_BIT even if you want to use OpenGL ES 3.

Values for attributes marked with EGL_DONT_CARE will be decided by the driver :

    EGLint numConfigs;
    EGLConfig windowConfig;
    eglChooseConfig(eglDisplay, configAttributes, &windowConfig, 1, &numConfigs);

 Step 4 - Create a window surface

Once we’ve got a valid configuration we can create a window surface that’ll be used for rendering :

    EGLint surfaceAttributes[] = { EGL_NONE };
    EGLSurface eglSurface = eglCreateWindowSurface(eglDisplay, windowConfig, hwnd, surfaceAttributes);

We don’t pass any special surface attributes (for more information see here), as most of them refer to OpenVG, so the default will e used, an EGL (back) render buffer.

Step 5 - Create a rendering context

Getting back to OpenGL terminology, we’re now ready to create our OpenGL ES render context, which is also the point at which you can specify the OpenGL ES version you want to use :

	EGLint contextAttributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
	EGLContext eglContext = eglCreateContext(eglDisplay, windowConfig, NULL, contextAttributes);

The EGL_CONTEXT_CLIENT_VERSION is (as of now) the only valid context creating attribute and determines the OpenGL ES version you want to use.

Step 6 - Render something :)

We’re now ready to put something onto the screen. We just need to make our EGL context current :

    eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

And your next lines can look like this :

    setupScene()
    while (!quit) {
		renderScene();
		eglSwapBuffers(eglDisplay, eglSurface);
    }

Closing words

screenshot

Using EGL for accessing OpenGL ES on your AMD (ATI) desktop GPU isn’t that hard, but since there isn’t much information available on the net, probably because the other IHVs make it a bit easer, and the examples from the AMD SDK didn’t work for me I decided to wrap my experiences into this small article.