EGL notes

From Android Wiki

Jump to: navigation, searcha

EGL is an API for giving direct control over creation of OpenGL contexts that render to on-screen windows, offscreen pixmaps, or additional graphics-card memory. GLSurfaceView provides a simple wrapper to save you using it directly, but you can still do so in cases where this isn’t flexible enough (like when you want to do offscreen rendering).

Contents

EGL Version

Android supports the additional attributes and display query target (but not the additional method calls) in EGL 1.2, even though the API docs only mention EGL 1.0. Also eglInitialize returns a major version of 1 and a minor version of 0.

Source(s): EGL solutions

Get EGL And A Display

First of all, get a reference to the global EGL library object by calling EGLContext.getEGL. To save repeated calls, you can stash this reference in a static global:

   public static final EGL10 EGL = (EGL10)EGLContext.getEGL();

All the library calls are now available through this EGL10 object. Next, you need to create yourself an EGLDisplay object and initialize it:

   final EGLDisplay MyDisplay = EGL.eglGetDisplay((EGLDisplay)EGL10.EGL_DEFAULT_DISPLAY);
   if (!EGL.eglInitialize(MyDisplay, null))
        ... report error ... 

Now you can use this to obtain suitable EGLConfigs for creating an EGLContext and an EGLSurface.

Source(s): EGL solutions

Configs

Each EGLConfig represents a mode of operation of the graphics hardware: number of bits per pixel, sizes of depth buffer and stencil buffer, compatibility with on-screen/offscreen use etc. eglGetConfigs will return all available configs (totalling 53 on my HTC Desire running Android 2.2), while eglChooseConfig will return only configs matching criteria that you specify.

If passed an array, these routines will return only as many config object as will fit. If passed null for the array argument, they will tell you how many configs can be potentially returned, so you know what size array to allocate. Here’s a utility wrapper around eglChooseConfig that returns an array sized exactly to hold the number of available configs:

   public static EGLConfig[] GetCompatConfigs
     (
       EGLDisplay ForDisplay,
       int[] MatchingAttribs
     )
     /* returns configs compatible with the specified attributes. */
     {
       EGLConfig[] Configs = null;
       final int[] ConfigsNr = new int[1];
       for (;;)
         {
           final boolean Success = EGL.eglChooseConfig
             (
               /*display =*/ ForDisplay,
               /*attrib =*/ MatchingAttribs,
               /*configs =*/ Configs,
               /*config_size =*/ Configs != null ? Configs.length : 0,
               /*num_config =*/ ConfigsNr
             );
           if (!Success)
             {
                ... report error ... 
               break;
             } /*if*/
           if (Configs != null)
               break;
           Configs = new EGLConfig[ConfigsNr[0]];
         } /*for*/
       return
           Configs;
     } /*GetCompatConfigs*/

An example call to this routine might be something like this:

       EGLConfig[] CompatConfigs = EGLUseful.GetCompatConfigs
         (
           /*ForDisplay =*/ MyDisplay,
           /*MatchingAttribs =*/ new int[]
               {
                   EGL10.EGL_RED_SIZE, 8,
                   EGL10.EGL_GREEN_SIZE, 8,
                   EGL10.EGL_BLUE_SIZE, 8,
                   EGL10.EGL_ALPHA_SIZE, 8,
                   EGL10.EGL_DEPTH_SIZE, 16,
                   EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
                   EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_NONE,
                   EGL10.EGL_NONE /* marks end of list */
               }
         );

Here is another useful routine, for querying an attribute of a config:

   public static int GetConfigAttrib
     (
       EGLDisplay ForDisplay,
       EGLConfig ForConfig,
       int Attrib
     )
     /* returns the value of the specified attribute of a config. */
     {
       final int[] AttrValue = new int[1];
       final boolean Success = EGL.eglGetConfigAttrib
         (
           /*dpy =*/ ForDisplay,
           /*config =*/ ForConfig,
           /*attribute =*/ Attrib,
           /*value =*/ AttrValue
         );
       if (!Success)
         {
           ... report error ...
         } /*if*/
       return
           AttrValue[0];
     } /*GetConfigAttrib*/

You might use it like this:

   int StencilSize = EGLUseful.GetConfigAttrib
     (
       /*ForDisplay =*/ MyDisplay,
       /*ForConfig =*/ CompatConfigs[0],
       /*Attrib =*/ EGL10.EGL_STENCIL_SIZE
     );

Source(s): EGL solutions

Offscreen Rendering

EGL supports (in principle) two different ways of doing offscreen 3D rendering: pbuffers and native pixmaps.

A pbuffer is a block of memory dynamically allocated on the graphics card, but not visible on-screen. The graphics hardware is capable of full accelerated rendering into such memory, just as it is with an on-screen buffer. After you have done rendering, you can copy the contents of the pbuffer into main memory for further processing, for example to composite it with other image data before showing it to the user. Note you have to use glReadPixels (e.g. GLES11.readPixels or GL10.glReadPixels) to read the contents of the pbuffer, EGLContext.eglCopyBuffers doesn’t seem to be implemented.

Unfortunately, glReadPixels is slow—slow enough to reduce frame rates of several hundred per second down to half a dozen per second. Like sucking the pixels back through a straw narrow enough to render pbuffers effectively useless.

A native pixmap uses whatever pixel buffer representation is supported by the platform’s native graphics API (in the case of Android, this would be a Bitmap), allowing you to mix and match OpenGL and native graphics drawing calls into the same pixels without having to copy buffers back and forth. Such configs tend to have EGL_CONFIG_CAVEAT attributes with a value of EGL_SLOW_CONFIG, which you would think is a hint to stay away from them; however, in practice they turn out to be reasonably fast to use, capable of a few dozen frames per second—certainly much better than pbuffers after you factor in the overhead of glreadPixels.

The main drawback with native pixmaps is that full-colour rendering with alpha channels doesn’t seem to be (directly) available. Even though the available configs list ones equivalent to ARGB_8888 bitmaps, the only ones I could get get to work were the equivalents of RGB_565 and RGB_ALPHA_8.

Source(s): Android solutions

Creating A Context

Whatever kind of rendering you’re going to do, on-screen or offscreen, you will need at least one Context, which you create according to the config you have decided to use:

       final EGLContext MyContext = EGL.eglCreateContext
         (
           /*display =*/ MyDisplay,
           /*config =*/ CompatConfigs[0],
           /*share_context =*/ EGL10.EGL_NO_CONTEXT,
           /*attrib_list =*/ null
         );

Using A Pbuffer

The last bit of EGL-specific setup you have to do is create a surface. The following routine facilitates easy creation of Pbuffer surfaces:

   public static EGLSurface CreatePbufferSurface
     (
       EGLDisplay ForDisplay,
       EGLConfig UseConfig,
       int Width,
       int Height,
       boolean ExactSize /* false to allow allocating smaller Width/Height */
     )
     {
       final int[] Attribs = new int[3 * 2 + 1];
         {
           int i = 0;
           Attribs[i++] = EGL10.EGL_WIDTH;
           Attribs[i++] = Width;
           Attribs[i++] = EGL10.EGL_HEIGHT;
           Attribs[i++] = Height;
           Attribs[i++] = EGL.EGL_LARGEST_PBUFFER;
           Attribs[i++] = ExactSize ? 0 : 1;
           Attribs[i++] = EGL10.EGL_NONE; /* marks end of list */
         }
       EGLSurface Result = null;
       Result = EGL.eglCreatePbufferSurface
         (
           /*display =*/ ForDisplay,
           /*config =*/ UseConfig,
           /*attrib_list =*/ Attribs
         );
       if (Result == EGL10.EGL_NO_SURFACE)
           ... report error ...
       return
           Result;
     } /*CreatePbufferSurface*/

Showtime

Finally, you set all the objects you’ve created as the current context:

   if (!EGL.eglMakeCurrent(MyDisplay, MySurface, MySurface, MyContext))
       ... report error ...

And now you actually have a GL10 or GL11 object you can use to make actual OpenGL calls:

   final GL10 gl = (GL10)MyContext.getGL();
   gl.glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
   gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
   ... etc ...

Source(s): EGL solutions

Getting The Pixels Back

After rendering to a pbuffer, you can read back the pixels with glReadPixels into a ByteBuffer:

   final ByteBuffer Pixels = ByteBuffer.allocateDirect
     (
       width * height * 4
     ).order(java.nio.ByteOrder.nativeOrder());
   gl.glReadPixels
     (
       /*x =*/ 0,
       /*y =*/ 0,
       /*width =*/ width,
       /*height =*/ height,
       /*format =*/ GL10.GL_RGBA,
       /*type =*/ GL10.GL_UNSIGNED_BYTE,
       /*pixels =*/ Pixels
     );

Then you could, for example, copy these pixels from the ByteBuffer into a Bitmap to use in further 2D drawing/compositing:

   Bitmap TheImageBits = Bitmap.createBitmap
     (
       /*width =*/ width,
       /*height =*/ height,
       /*config =*/ Bitmap.Config.ARGB_8888
     );
   TheImageBits.copyPixelsFromBuffer(Pixels);

Note, however, that OpenGL will return the pixel scanlines going upwards, not downwards. So if you draw this bitmap into a Canvas using drawBitmap, it will appear upside-down unless you apply a Matrix with a Y-scaling of -1.

Source(s): EGL solutions

Keeping Things Tidy

To be kind to other apps, you should release your context from being current in your onPause handler, as follows:

   EGL.eglMakeCurrent(MyDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

and use the previous eglMakeCurrent call to re-establish your current context in onResume.

You also need to do this release after you have finished making use of OpenGL, after which you can free up all your allocated EGL resources with one call:

   EGL.eglTerminate(MyDisplay);

Missing API Definitions

In EGL 1.2, eglQueryString can take an additional target value, EGL_CLIENT_APIS, but this does not appear in the Android API. You can define this as follows:

   public static final int EGL_CLIENT_APIS = 0x308D;

On my HTC Desire (Android 2.2), the value returned from this is “OpenGL ES”, even though the EGL spec says it has to contain “OpenGL_ES”.

Also, while the config attribute EGL_RENDERABLE_TYPE is defined and usable, the names for the bitmasks for the returned value are not. These can be defined as follows:

   public static final int EGL_OPENGL_ES_BIT = 0x0001;
   public static final int EGL_OPENGL_ES2_BIT = 0x0004; /* API level ≥ 8 only */

See Also

Documentation on OpenGL, OpenGL-ES and EGL can be found at khronos.org.

Sample Code

  • 3D-Compass illustrates how to use offscreen OpenGL calls to do compositing of 3D graphics with the camera preview image.
  • On-versus-Offscreen demonstrates various ways of displaying the same OpenGL animation, comparing direct on-screen rendition (the fastest) with four different ways of doing it offscreen.
Personal tools