Using EGL to connect a native window and OpenGL ES

EGL provides an interface between OpenGL ES, which is a rendering API, and the Screen API, which is our native windowing system. Generally, you establish contexts between the two APIs, then create surfaces to render to. The BlackBerry 10 OS and the BlackBerry PlayBook OS both support EGL 1.4. This section describes how to use EGL to connect a native window with OpenGL ES. It describes how to set up EGL and close the EGL resources, as well as how to use EGL surfaces, contexts, and configurations.

Set up EGL

This section outlines how to set up EGL in your app.

Declare the libraries and variables

First, in the header file, add the EGL (libEGL) and Screen Graphics Subsystem (libscreen) libraries, and an extra library that describes platform types. You should also declare externally accessible variables for the EGL display and surface.

Not applicable

Not applicable

#include <EGL/egl.h>
#include <screen/screen.h>
#include <sys/platform.h>

extern EGLDisplay egl_disp;
extern EGLSurface egl_surf;

In the associated source file, include the header file, some standard C libraries, and the OpenGL ES version that you're using. Your EGL setup accommodates either OpenGL ES 1.1 or OpenGL ES 2.0, depending on the macro definition that you use. Each macro definition corresponds to an OpenGL ES: USING_GL11 for OpenGL ES 1.1 and USING_GL20 for OpenGL ES 2.0.

You can reuse this code for either version to set up EGL. Add the definition directly to your code or add it to the common.mk file. After you define the macro, add the proper inclusion library. Later, you can reuse these macros for OpenGL ES version-specific tasks.

Not applicable

Not applicable

#include <assert.h>
#include <ctype.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/keycodes.h>
#include <time.h>
#include <stdbool.h>
#include <math.h>
#include "bbutil.h"

#ifdef USING_GL11
#include <GLES/gl.h>
#include <GLES/glext.h> 
#elif defined(USING_GL20)
#include <GLES2/gl2.h>
#else
#error bbutil must be compiled with either USING_GL11 or USING_GL20 flags
#endif

Next, declare some variables to use for EGL and the native window. To set up EGL, you need an EGL surface, EGL configuration, and EGL context. To set up a native window, you need a native context handle, a native window handle, and a native display handle. You can use the rc variable to check the value that is returned for errors. Use the num_configs variable to describe the number of EGL configurations and use the usage variable to specify the bitmask for the native window use.

Not applicable

Not applicable

    EGLDisplay egl_disp;
    EGLSurface egl_surf;
    static EGLConfig egl_conf;
    static EGLContext egl_ctx;

    static screen_context_t screen_ctx;
    static screen_window_t screen_win;
    static screen_display_t screen_disp;

    int rc, num_configs;
    int usage;

Define the configuration and context

Declare a format variable and set it to SCREEN_FORMAT_RGBX8888. This format specifies a 32-bit RGB color space with no alpha channel for your window. Then, create your attribute list for the EGL color buffer configuration using the attribute-value pairing. EGL_NONE means that you're ending the attribute list. For more information about attribute-value pairs, see the eglChooseConfig() manual pages.

Not applicable

Not applicable

    int format = SCREEN_FORMAT_RGBX8888;
    EGLint attrib_list[]= { EGL_RED_SIZE,        8,
                            EGL_GREEN_SIZE,      8,
                            EGL_BLUE_SIZE,       8,
                            EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,
                            EGL_RENDERABLE_TYPE, 0,
                            EGL_NONE};

You also need to handle some OpenGL ES version-specific tasks for the native window usage and color buffer configuration. Set the usage variable for each OpenGL ES version, then combine it with the rotation flag that specifies that the window can be rotated. Also, set the value of EGL_RENDERABLE_TYPE attribute to the version constant that corresponds to which OpenGL ES you're using. The EGL_OPENGL_ES_BIT constant corresponds to OpenGL ES 1.1 and EGL_OPENGL_ES2_BIT constant corresponds to OpenGL ES 2.0. Last, if you use OpenGL ES 2.0, you must create an attributes array to specify the version you're using. You don't have to specify an attributes array for OpenGL ES 1.1, because the default value is 1. You can use the attributes array later, when you create the EGL context.

Not applicable

Not applicable

#ifdef USING_GL11
    usage = SCREEN_USAGE_OPENGL_ES1 | SCREEN_USAGE_ROTATION;
    attrib_list[9] = EGL_OPENGL_ES_BIT;
#elif defined(USING_GL20)
    usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_ROTATION;
    attrib_list[9] = EGL_OPENGL_ES2_BIT;
    EGLint attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
#else
    fprintf(stderr, "bbutil should be compiled with either USING_GL11 or USING_GL20 -D flags\n");
    return EXIT_FAILURE;
#endif

Now that you declared the variables and set the configuration, you can start initializing. First, you need a screen context and a handle to the default EGL display. Then, you can initialize EGL and set EGL to use only OpenGL ES.

Not applicable

Not applicable

    static screen_context_t screen_cxt;
    screen_create_context(&screen_cxt, 0);

    egl_disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (egl_disp == EGL_NO_DISPLAY) {
        bbutil_egl_perror("eglGetDisplay");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglInitialize(egl_disp, NULL, NULL);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglInitialize");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglBindAPI(EGL_OPENGL_ES_API);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglBindApi");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Using the attrib_list array that you defined earlier, you must choose the color buffer configuration that you want to use. Then, you can create your EGL context by calling eglCreateContext(). How you create the EGL context depends on the version of OpenGL ES that you use. If you use OpenGL ES 2.0, the fourth parameter is your attributes array. However, if you use OpenGL ES 1.1, the argument can be NULL because 1 is the default value that also specifies that you're using OpenGL ES 1.1.

Not applicable

Not applicable

    if(!eglChooseConfig(egl_disp, attrib_list, &egl_conf, 1, &num_configs)) {
        bbutil_terminate();
        return EXIT_FAILURE;
    }

#ifdef USING_GL20
        egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, attributes);
#elif defined(USING_GL11)
        egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, NULL);
#endif

    if (egl_ctx == EGL_NO_CONTEXT) {
        bbutil_egl_perror("eglCreateContext");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Create the native window

Next, you set up your native window. This process is similar to how you set up the basic native window, but you add a few new aspects. Start by creating the window and setting the usage and format properties.

Not applicable

Not applicable

    rc = screen_create_window(&screen_win, screen_ctx);
    if (rc) {
        perror("screen_create_window");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format);
    if (rc) {
        perror("screen_set_window_property_iv(SCREEN_PROPERTY_FORMAT)");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);
    if (rc) {
        perror("screen_set_window_property_iv(SCREEN_PROPERTY_USAGE)");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Then, get the handle of the native display and the angle of the device orientation as an int value. You also get the screen mode using the display handle and get the current buffer size of the window.

Not applicable

Not applicable

    rc = screen_get_window_property_pv(screen_win, SCREEN_PROPERTY_DISPLAY, (void **)&screen_disp);
    if (rc) {
        perror("screen_get_window_property_pv");
        bbutil_terminate();
        return EXIT_FAILURE;
    }
    int angle = atoi(getenv("ORIENTATION"));

    screen_display_mode_t screen_mode;
    rc = screen_get_display_property_pv(screen_disp, SCREEN_PROPERTY_MODE, (void**)&screen_mode);
    if (rc) {
        perror("screen_get_display_property_pv");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    int size[2];
    rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, size);
    if (rc) {
        perror("screen_get_window_property_iv");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Next, you adjust the buffer depending on the device that you want to target and how the device is held. If you target only one device, you can use the orientation angle to exchange the width and height that you know, as needed. However, if you want to target multiple devices, adjusting the buffer gets a little more complicated. If you think about how to support multiple devices and orientations, you see a few different scenarios that you need to handle.

If you hold a device with the BlackBerry logo facing you, there are three possible screen dimensions:

  • The width is smaller than the height (portrait orientation)
  • The width is greater than the height (landscape orientation)
  • The width equals the height

For the last scenario, you don't need to adjust the buffer, since the dimensions are the same. It is the first and second cases that you need to handle.

With different devices and orientations, the buffer size that's appropriate depends on the screen dimensions of a device, along with its orientation. You can use screen_mode.width and screen_mode.height to obtain the dimensions of the screen, independent of the device orientation. You obtained the orientation angle of the device previously as the angle variable.

An angle of 0 refers to a device held in the default portrait orientation with the BlackBerry logo facing you, and an angle of 180 refers to a device in portrait orientation with the BlackBerry logo upside down. An angle of 90 or 270 refers to a device held in landscape orientation.

Not applicable

Not applicable

    int buffer_size[2] = {size[0], size[1]};

    if ((angle == 0) || (angle == 180)) {
        if (((screen_mode.width > screen_mode.height) && (size[0] < size[1])) ||
            ((screen_mode.width < screen_mode.height) && (size[0] > size[1]))) {
                buffer_size[1] = size[0];
                buffer_size[0] = size[1];
        }
    } else if ((angle == 90) || (angle == 270)){
        if (((screen_mode.width > screen_mode.height) && (size[0] > size[1])) ||
            ((screen_mode.width < screen_mode.height && size[0] < size[1]))) {
                buffer_size[1] = size[0];
                buffer_size[0] = size[1];
        }
    } else {
         fprintf(stderr, "Navigator returned an unexpected orientation angle.\n");
         bbutil_terminate();
         return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, buffer_size);
    if (rc) {
        perror("screen_set_window_property_iv");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &angle);
    if (rc) {
        perror("screen_set_window_property_iv");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Next, you attach buffers to the native window. A native window requires one buffer to become visible. However, native windows that use OpenGL ES need at least two buffers to avoid flickering, artifacts, or tearing on the scene that is displayed. Attaching only one buffer means that you change the same buffer that is displayed. If you have two buffers, you can make changes in one buffer, while the other buffer is displayed, then swap the buffers to show the changes. So, you call screen_create_window_buffers() function passing 2 as an argument, to allocate two buffers.

Not applicable

Not applicable

    rc = screen_create_window_buffers(screen_win, 2);
    if (rc) {
        perror("screen_create_window_buffers");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Create an EGL surface

Now, you need a surface to display on. For this example, you create a window surface. Set the window surface as the current surface to display to using the eglMakeCurrent() function. Because you're using a window surface, you can use the eglSwapBuffers() function to post the updated surface, but you must set the interval for how frequently you want to swap buffers. You call the eglSwapInterval() function, passing a value of 1 to specify that there must be at least one frame displayed before the buffers are swapped.

Not applicable

Not applicable

    egl_surf = eglCreateWindowSurface(egl_disp, egl_conf, screen_win, NULL);
    if (egl_surf == EGL_NO_SURFACE) {
        bbutil_egl_perror("eglCreateWindowSurface");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_ctx);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglMakeCurrent");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglSwapInterval(egl_disp, 1);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglSwapInterval");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

Because you're using a window surface, you use the eglSwapBuffers() function to show the changes that you made to your model.

Not applicable

Not applicable

    int rc = eglSwapBuffers(egl_disp, egl_surf);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglSwapBuffers");
    }

Close the EGL resources

When you're ready to close your app, you must shut down the EGL components that you used.

First, you set the current surface to EGL_NO_SURFACE. Then, you destroy the surface, EGL context, and native window.

Not applicable

Not applicable

    if (egl_disp != EGL_NO_DISPLAY) {
        eglMakeCurrent(egl_disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        if (egl_surf != EGL_NO_SURFACE) {
            eglDestroySurface(egl_disp, egl_surf);
            egl_surf = EGL_NO_SURFACE;
        }
        if (egl_ctx != EGL_NO_CONTEXT) {
            eglDestroyContext(egl_disp, egl_ctx);
            egl_ctx = EGL_NO_CONTEXT;
        }
        if (screen_win != NULL) {
            screen_destroy_window(screen_win);
            screen_win = NULL;
        }

You terminate the EGL display connection and set it to EGL_NO_DISPLAY. You return EGL to its state at thread creation by calling the eglReleaseThread() function.

Not applicable

Not applicable

        eglTerminate(egl_disp);
        egl_disp = EGL_NO_DISPLAY;
    }
    eglReleaseThread();

Set up an EGL configuration

EGL uses configurations that are set through EGLConfig objects to describe the format, type, and size of the color buffers and ancillary buffers for an EGLSurface. You can specify attributes for a configuration in an array with attribute-value pairing. Then, you can call eglChooseConfig(), passing that attribute list. Here's an example:

Not applicable

Not applicable

EGLDisplay display;
EGLSurface surface;

static EGLConfig config;
static EGLContext context;
int num_configs
EGLint attrib_list[]= { EGL_RED_SIZE,        8,
                        EGL_GREEN_SIZE,      8,
                        EGL_BLUE_SIZE,       8,
                        EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,
                        EGL_RENDERABLE_TYPE, 0,
                        EGL_NONE};

...

eglChooseConfig(display, attrib_list, &config, 1, &num_configs);

If you include multiple bitmasks, combine the bitmasks with a bitwise OR. For example, if you use window and pixmap surfaces, the EGL_SURFACE_TYPE attribute would be EGL_WINDOW_BIT | EGL_PIXMAP_BIT. For a complete listing of attributes, see the eglChooseConfig() manual page.

Use EGL rendering contexts

The rendering context, EGLContext, defines the relationship between a rendering API and the native windowing system (Screen API). You set EGL contexts on a per-thread basis, so you set one context as the current context for a particular thread. Also, your EGL context can be bound to one EGL surface.

Set the rendering API

When you use EGL to connect the native window system to OpenGL ES, you must bind the API that you're using to a particular thread. You set rendering APIs on a per-thread basis.

To set the current rendering API for the thread, you can make the following call:

Not applicable

Not applicable

    eglBindAPI(EGL_OPENGL_ES_API);

To query which API is used on a thread, within the thread, you call:

Not applicable

Not applicable

    eglQueryAPI();

Create an EGL rendering context

As well as connecting OpenGL ES to the Screen API, the EGL rendering context also binds OpenGL ES rendering calls to an EGL surface. The OpenGL ES calls that you make are applied to the current EGL surface. The EGL surface and EGL context need compatible framebuffer configurations to render correctly. These framebuffer configurations are defined as an attrib_list array. To create an EGL rendering context, you can use the following call:

Not applicable

Not applicable

    eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, EGLint const * attrib_list);

Your EGLDisplay and EGLConfig objects should be initialized first. The third parameter specifies the EGLContext for shared texture objects. If sharing is not required, you can use EGL_NO_CONTEXT. The last parameter is reserved for attributes that specifiy which version of OpenGL ES you want to use and the framebuffer configuration. If the operation fails, EGL_NO_CONTEXT is returned. For more information, see the eglCreateContext() manual page.

Bind a context to draw and read surfaces

You bind your EGL rendering context to an EGL draw surface and an EGL read surface by calling eglMakeCurrent(). When you bind the current context and the current surface for a thread, all of your OpenGL ES rendering calls are used with that context and surface until you call eglMakeCurrent() with different parameters. Here's a code sample that shows you how to call eglMakeCurrent():

Not applicable

Not applicable

    eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context);

Destroy an EGL rendering context

You can destroy contexts using the eglDestroyContext() function. However, you must ensure that the context you destroy isn't set as the current context, so you call eglMakeCurrent() first.

Not applicable

Not applicable

    eglMakeCurrent(EGLDisplay display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(EGLDisplay display, EGLContext context);

Create EGL rendering surfaces

You can use the following EGL rendering surfaces to display your OpenGL ES model:

  • Window surface: for on-screen rendering
  • Pixmap surface: for off-screen rendering into buffers
  • Pixel buffer surface (pbuffer): for off-screen rendering

For all of the following surface types, the EGLSurface that you created is returned. If the surface isn't created because of an error, EGL_NO_SURFACE is returned.

Create a window surface

To create a window surface, you call the eglCreateWindowSurface() function. Before you call this function, you should initialize your EGLDisplay, EGLConfig, and native window ( screen_window_t). You also need to ensure that EGL_SURFACE_TYPE in your EGL configuration is set to EGL_WINDOW_BIT. The last parameter is reserved for surface attributes using attribute-value pairs, but you can set this parameter to NULL. For more information on valid attribute-value pairs, see the eglCreateWindowSurface() API reference.

Not applicable

Not applicable

EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, screen_window_t window,  EGLint const * attrib_list );

Create a pixmap surface

To create a pixmap surface, you call the eglCreatePixmapSurface() function. Before you call this function, you should initialize your EGLDisplay, EGLConfig, and native pixmap ( screen_pixmap_t). You create native pixmaps by calling the screen_create_pixmap() function. You also need to ensure that EGL_SURFACE_TYPE in your EGL configuration is set to EGL_PIXMAP_BIT. Again, the last parameter is reserved for surface attributes. For more information on the attribute-value pairs, see the eglCreatePixmapSurface() API reference.

Not applicable

Not applicable

EGLSurface eglCreatePixmapSurface(EGLDisplay display, EGLConfig config, screen_pixmap_t pixmap,  EGLint const * attrib_list );

Create a pixel buffer surface

To create a pixel buffer surface, you call the eglCreatePbufferSurface() function. The parameters are similar to the eglCreateWindowSurface() and eglCreatePixmapSurface() calls, except there is no native window or native pixmap given. The last parameter is reserved for attributes, but you can set this parameter to NULL. You should ensure that your EGL configuration has EGL_SURFACE_TYPE set to EGL_PIXMAP_BIT. For more information on valid attribute-value pairs, see the eglCreatePbufferSurface() API reference.

Not applicable

Not applicable

EGLSurface eglCreatePbufferSurface(EGLDisplay display, EGLConfig config,  EGLint const * attrib_list );

Render to textures

To render an OpenGL ES texture to a pixel buffer surface, you can call the eglBindTexImage() function. The texture image is a buffer of the image data, which is the last parameter. You set texture properties, such as the texture target, texture format, and size of the texture components, as the attributes of the surface.

Not applicable

Not applicable

eglBindTexImage(EGLDisplay display, EGLSurface surface, EGLint buffer) 

After you bind the texture to the surface, you can release the color buffer and texture by calling the eglReleaseTexImage() function.

Not applicable

Not applicable

eglReleaseTexImage(EGLDisplay display, EGLSurface surface, EGLint buffer)

Exchange color buffers

To display changes that you made to the native window, swap the color buffers:

Not applicable

Not applicable

eglSwapBuffers(EGLDisplay display, EGLSurface surface);

If you have a window surface with at least two buffers, then the color buffer is copied to the native window that is associated with that surface. To set the minimum number of frames that are displayed before a buffer swap, make the following call:

Not applicable

Not applicable

eglSwapInterval(EGLDisplay display, int num_frames);

To copy the color buffer to a native pixmap, call eglCopyBuffers():

Not applicable

Not applicable

eglCopyBuffers(EGLDisplay display, EGLSurface surface, screen_pixmap_t pixmap);

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus