Screen supports the following Khronos rendering APIs: OpenGL ES and OpenVG. They provide common interfaces to graphics hardware that allow users to generate and manipulate high quality graphics images of two-dimensional vector and three-dimensional graphics.
Khronos rendering APIs can provide user applications with the following key functions:
OpenGL ES and OpenVG standards are published by Khronos Group.
Typically, hardware vendors have their own implementation of the Khronos standards that takes advantage of hardware acceleration and, in particular, the GPU hardware. In order to provide a common interface to Screen applications, the Khronos rendering API calls are redirected onto appropriate vendor-specific rendering API functionality through the QNX libraries provided by Screen.
Khronos standards are implemented specifically for the hardware vendor, and so, performance varies between different implementations.
Screen's EGL library (libEGL) provides access to the vendor-specific implementation of the Khronos EGL. Khronos EGL is the interface between rendering APIs and the underlying windowing environment, in this case, Screen. It provides functions to enable graphics context management, surface building and binding, as well as providing synchronization between different rendering tasks. In summary, the Khronos rendering APIs provide the content, and EGL provides access to wrap Screen's render target.
Key functions provided by Khronos EGL are:
Generally, the main steps you'll need to do in your application to render using hardware are:
The following examples use OpenGL ES as the rendering API, but you can apply OpenVG similarly where applicable.
This contains the buffer or buffers that your application will render to.
The steps to create your render target for hardware rendering are very similar to those that you'd do for software rendering. The key difference is setting the usage bit of your render target's SCREEN_PROPERTY_USAGE flag.
First, you need to create a target to which to render. For example:
... screen_context_t screen_ctx; screen_window_t screen_win; ... screen_create_window(&screen_win, screen_ctx); ...
Now that you've created your target, you must set the appropriate properties on this target. At a minimum, you should set the usage. For example, here's what the code might look like for creating a window:
... int usage, format; int interval = 1; int size[2] = { 200, 200 }; /* Size of window */ int pos[2] = { 0, 0 }; /* Position of window */ ... /* Indicate that OpenGL ES 1.x will render to the buffer associated with this render target. */ usage = SCREEN_USAGE_OPENGL_ES1; format = SCREEN_FORMAT_RGBA8888; /* Indicate the pixel format to be used. */ ... screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage); screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format); screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SWAP_INTERVAL, &interval); screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, size); screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_POSITION, pos); ...
And finally, you'll need to create some buffers for your render target. For example:
... int nbuffers = 2; /* Number of window buffers */ ... screen_create_window_buffers(screen_win, nbuffers);
Among other things, this context keeps track of the EGL state. Creating a rendering context includes:
Creating a rendering context requires that you use the EGL library. Therefore, you must include the appropriate header file (#include <EGL/egl.h>) in your application.
Connect to and initialize your EGL display
One of the first things you'll need to do when creating a rendering context is to establish a connection to an EGL display, and then initialize it:
... EGLDisplay egl_disp; ... egl_disp = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(egl_disp, NULL, NULL); ...
Choose your EGL configuration
Choosing an appropriate EGL configuration is an important part of creating your rendering context. What you're actually choosing is the pixel format configuration (SCREEN_PROPERTY_FORMAT) of your render target. The EGL configuration must match, in the least, the following attributes of your render target:
In embedded systems, the difference in performance between pixel formats can make or break an application. For example, on desktop systems, a pixel format of RGB111 or better is usually sufficient because this pixel format returns the best configuration supported by the hardware. However, on embedded systems, RGBA8888 may not be an option. Even if RGBA8888 is supported by your rendering hardware, the system may not be able to handle the memory bandwidth required by the display controller to paint the display at 60 frames per second.
The format chosen may impact performance and memory usage. The impact may vary depending on things, such as your display controller and blitter.
You have several options for choosing your EGL configuration, some of which are:
Specifying an EGL configuration by its ID (EGL_CONFIG_ID) gives you the ability to get exactly what you want. However, this approach is far from being user-friendly when there are multiple platforms and windowing systems to consider.
EGL's eglChooseConfig() function is probably the most complicated of all its functions. If multiple EGL framebuffer configurations that match all attributes you provided are found, then a list of these configurations is returned. This list is sorted according to the best match criteria as described by the eglChooseConfig() specification from Khronos.
There are many attributes that you can specify, each with its own matching rules, default value, and sorting order. It's easy to get confused with all the special rules, ending up with the wrong configuration, or no configuration, without understanding why.
EGL's eglGetConfigs() function can be used to get all the EGL configurations. Once you have all the EGL configurations, search for one that matches your render target's specified attributes.
You'll need to call eglGetConfigs() twice. The first time you call it, you'll get the total number of configurations returned. With this number, you'll know how much memory to allocate in order to store the configurations so that you can search through them to match for your render target's attributes. For example:
... EGLConfig *egl_configs; EGLint egl_num_configs; ... eglGetConfigs(egl_disp, NULL, 0, &egl_num_configs); egl_configs = malloc(egl_num_configs * sizeof(*egl_configs)); eglGetConfigs(egl_disp, egl_configs, egl_num_configs, &egl_num_configs); ...
Then, loop through each configuration returned from eglGetConfigs() and check for matching attributes. For example:
... struct { EGLint surface_type; EGLint red_size; EGLint green_size; EGLint blue_size; EGLint alpha_size; EGLint samples; EGLint config_id; } egl_conf_attr; ... EGLint val; EGLConfig egl_conf = (EGLConfig)0; /* Resulting EGL config */ ... /* Make sure the surface type matches that of your render target */ eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_SURFACE_TYPE, &val); if ((val & egl_conf_attr.surface_type) != egl_conf_attr.surface_type) { continue; } /* Make sure the renderable type has the OpenGL ES bit. */ eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_RENDERABLE_TYPE, &val); if (!(val & EGL_OPENGL_ES_BIT)) { continue; } /* Check each attribute you want for your render target. */ ...
Once you've found an acceptable configuration for your render target, store it (egl_conf = egl_configs[i];) and stop looking. Clean up any resources you created to support your search.
Create a rendering context
... EGLContext egl_ctx; ... egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, NULL); ...
Create EGL Surface
Use the render target that you've created (e.g., a native Screen window) to create an EGL surface. The EGL surface extends your native window with additional auxiliary buffers if necessary. These auxiliary buffers may refer to color, depth, or stencil. Use the same display and configuration for the EGL surface as you used to create your render target.
... EGLSurface egl_surf; struct { EGLint render_buffer[2]; EGLint none; } egl_surf_attr = { .render_buffer = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER }, /* Ask for double-buffering */ .none = EGL_NONE /* End of list */ }; ... egl_surf = eglCreateWindowSurface(egl_disp, egl_conf, screen_win, (EGLint*)&egl_surf_attr);
Bind your render target
Use eglMakeCurrent() to bind your render target and your read/draw surfaces to the current thread.
eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_ctx);
The display and context are just those that you've established in your previous steps. The draw surface is your EGL surface you've created by specifying your render target. It's used for all GL operations except for any pixel data readbacks. GL operations that read pixels are taken from the framebuffer of your read surface. The read and draw surfaces are typically one and the same.
Render
At this point, you can start rendering as part of your main application loop.
Unlike software rendering, you use eglSwapBuffers() to post your new frame -- even if you're not using multiple buffers.
eglSwapBuffers(egl_disp, egl_surf);
Notice that unlike with software rendering, you don't have the option to specify dirty rectangles with eglSwapBuffers() as you did with screen_post_window(), or screen_post_stream(). If your application needs to specify a region of dirty rectangles in its request for a buffer swap, there is an EGL extension (EXT_swap_buffers_with_damage) that Screen supports. You'd use this extension to allow Screen to optimize the amount of compositing it needs to do when your application is animating only a small region of the rendering surface.
With hardware rendering, the time it takes to return from a swap buffer request depends on the GPU architecture of your hardware. When the rendering starts and stops, and when the actual post occurs all depend on the architecture of the GPU.