EGL notes

From Android Wiki

Revision as of 10:44, 26 September 2011 by Ldo (Talk | contribs)
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.

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.

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
     );

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.

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. However, when I tried allocating such EGL surfaces on my HTC Desire (running Android 2.2), the only compatible configs I could find had EGL_CONFIG_CAVEAT attributes with a value of EGL_SLOW_CONFIG, which is probably a good hint to stay away from them.

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 ...

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.

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.

Personal tools