Using OpenGL ES

This chapter shows you how to use OpenGL® ES in the QNX Graphics Framework.

It includes:

OpenGL ES is subset of the OpenGL API designed for 3D graphics on embedded systems. The QNX Graphics Framework is OpenGL ES 1.0 Common Profile API Certified. For more information about the standard, see http://www.khronos.org/opengles/.

OpenGL ES Logo

Certain constructs within the EGL API are mapped to equivalent constructs within the GF API:

EGL construct GF construct
NativeDisplayType gf_dev_t
NativeWindowType gf_3d_target_t
NativePixemapType gf_surface_t
EGL_NATIVE_VISUAL_ID GF layer format

Using OpenGL ES

In order to write an application that uses the OpenGL ES and EGL interfaces under QNX Neutrino, the application must also use the GF library to interface with the QNX Graphics Framework.

The application must first attach to a device using gf_dev_attach(). The resulting handle (a gf_dev_t) is passed to eglGetDisplay() to initialize an EGL display connection.


Note: The code examples in this chapter are adapted from sample applications included with QNX Advanced Graphics .

For example:



static EGLint attribute_list[] = {
    EGL_NATIVE_VISUAL_ID, 0,
    EGL_RED_SIZE, 5,
    EGL_GREEN_SIZE, 5,
    EGL_BLUE_SIZE, 5,
    EGL_DEPTH_SIZE, 16,
    EGL_NONE
};

...

    gf_dev_t                gf_dev;
    gf_dev_info_t           info;


    /* initialize the graphics device */
    if (gf_dev_attach(&gf_dev, NULL, &info) != GF_ERR_OK) {
            perror("gf_dev_attach()");
            return -1;
    }

...

    /* get an EGL display connection */
    display = eglGetDisplay(gf_dev);

    if (display == EGL_NO_DISPLAY) {
            fprintf(stderr, "eglGetDisplay() failed\n");
            return -1;
    }

Next, the application uses the GF API to attach to a display, and then to a layer on that display. For an example, see the Setting up GF section of the Basic Drawing chapter.

Using the EGL API, the application must now find a buffer configuration that's compatible with the layer it will use to display 3D content. The color buffer (the buffer being rendered to) and the display buffer (the buffer that holds the display contents being displayed on the selected layer) must have identical formats. This enables flicker-free double buffering with minimal overhead.

Using the GF API, the application can query the display buffer formats supported by the selected layer by calling gf_layer_query(). It can then call eglChooseConfig() to determine which buffer configurations, if any, are compatible with a given display buffer format. The key to performing this match is to use the EGL_NATIVE_VISUAL_ID attribute to select the EGL buffer configurations that support a given format. For example:

for (i = 0; ; i++) {
    /* Walk through all possible pixel formats for this layer */
    if (gf_layer_query(layer, i, &linfo) != GF_ERR_OK) {
        fprintf(stderr, "Couldn't find a compatible frame "
            "buffer configuration on layer %d\n", layer_idx);
        exit(EXIT_FAILURE);
    }

    /*
     * We want the color buffer format to match the layer format,
     * so request the layer format through EGL_NATIVE_VISUAL_ID.
     */
    attribute_list[1] = linfo.format;

    /* Look for a compatible EGL frame buffer configuration */
    if (eglChooseConfig(display,
        attribute_list, &config, 1, &num_config) == EGL_TRUE) {
        if (num_config > 0) {
            format_idx = i;
            break;
        }
    }
}

Creating surfaces

Once the application has selected an EGL buffer configuration, it may create rendering contexts to render with, and surfaces to render into. The surface types that you can render into are:

Window surfaces

To create a window surface, the application must first create a native GF 3D rendering target by calling gf_3d_create_target(). The application may specify a list of preallocated native GF surfaces to gf_3d_create_target(), for rendering into. Note that the GF 3D library must be able to target these surfaces. That is, the GF_SURFACE_CREATE_3D_ACCESSIBLE flag must have been specified when they were created. Typically, the application would specify two surfaces for rendering into so it could use double buffering. If the application passes only a single surface, or if it does not pass a list of surfaces at all, then the GF library will attempt to internally allocate as many buffers as are necessary. Upon successful creation of a 3D rendering target, you can pass the resulting handle to eglCreateWindowSurface() to create a surface that can be used for OpenGL ES rendering.

For example:

/* create a 3D rendering target */
if (gf_3d_target_create(&target, layer,
    NULL, 0, width, height, linfo.format) != GF_ERR_OK) {
    fprintf(stderr, "Unable to create rendering target\n");
    return NULL;
}

...

/* create an EGL window surface */
surface = eglCreateWindowSurface(display, config, target, NULL);

if (surface == EGL_NO_SURFACE) {
    fprintf(stderr, "Create surface failed: 0x%x\n", eglGetError());
    exit(EXIT_FAILURE);
}

Pixmap surfaces

To create a pixmap surface, the application must create a native GF surface using the GF API, and then pass the surface handle to eglCreatePixmapSurface() as the pixmap argument. In order to allocate the GF surface so that it's compatible with a given EGL configuration ID, the application should call gf_3d_query_config() to determine the surface format and flags that it must pass to the surface creation function.

/*
 * We want to allocate a surface that EGL can use to create
 * a Pixmap surface
 */
if (gf_3d_query_config(&cfginfo, gf_dev, display, chosen) != GF_ERR_OK) {
    fprintf(stderr, "query native failed\n");
    exit(EXIT_FAILURE);
}

if (gf_surface_create(&gfsurface, gf_dev, WIDTH, HEIGHT,
    cfginfo.surface_format, NULL, cfginfo.create_flags) != GF_ERR_OK) {
    fprintf(stderr, "create surface failed\n");
    exit(EXIT_FAILURE);
}

pmsurface = eglCreatePixmapSurface(display, chosen, gfsurface, NULL);

if (pmsurface == EGL_NO_SURFACE) {
    fprintf(stderr, "Create Pixmap failed: 0x%x\n", eglGetError());
    exit(EXIT_FAILURE);
}

pbuffer surfaces

To create a pbuffer surface, the application must specify the width and height of the surfaces via the EGL_WIDTH and EGL_HEIGHT attributes. In the case of a pbuffer surface, the actual surface memory is always allocated internally by OpenGL ES.

/* create an EGL pbuffer surface */
pbsurface = eglCreatePbufferSurface(display, chosen, pb_attrs);

if (pbsurface == EGL_NO_SURFACE) {
    fprintf(stderr, "Create PBuffer failed: 0x%x\n", eglGetError());
    exit(EXIT_FAILURE);
}

/* connect the context to the PBuffer surface */
if (eglMakeCurrent(display, pbsurface, pbsurface, context) == EGL_FALSE) {
    fprintf(stderr, "Make current failed: 0x%x\n", eglGetError());
    exit(EXIT_FAILURE);
}

Using Vertex Buffer Objects

This appendix contains information about supported OpenGL ES extensions, and enhancements to the OpenGL ES specification.

The QNX Neutrino implementation of OpenGL ES 1.0 is extended to support Buffer Objects, which is functionality defined by the OpenGL 1.5 specification. However, only the subset of functionality defined by the OpenGL ES Common/Common Lite Profile Specification, Version 1.1 is supported.

This extension allows per-vertex data to be stored in higher-performance memory, to enable faster rendering. For example, on some hardware, the data could be cached in frame buffer memory, which can allow for faster processing, and reduced CPU overhead.

Buffer Objects support is available regardless of the hardware present. However, at this time, a significant performance improvement can only be achieved through the use of Buffer Objects when using the Fujitsu Carmine graphics controller.

To use VBOs, your application should include <GLES/glext.h>. The buffer object extension is named GL_OES_vertex_buffer_object.