Besides the GLSurfaceView
display mechanism we
described in the previous recipe, it is also possible to display OpenGL graphics using EGL.
Readers are recommended to read the Drawing 3D Graphics and Lighting up the Scene with OpenGL ES 1.x API recipe before going through this one.
The following steps describe how to create an Android project that demonstrates the usage of EGL:
EGLDemo
. Set the package name as cookbook.chapter4.egl
. Please refer to the Loading native libraries and registering native methods recipe in Chapter 2, Java Native Interface, if you want more detailed instructions.EGLDemo
, select Android Tools | Add Native Support.EGLDemoActivity.java
and MySurfaceView.java
. EGLDemoActivity.java
sets ContentView
as an instance of MySurfaceView
, and starts and stops rendering at the Android activity callback functions:… … public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); myView = new MySurfaceView(this); this.setContentView(myView); } protected void onResume() { super.onResume(); myView.startRenderer(); } … … protected void onStop() { super.onStop(); myView.destroyRender(); } … …
MySurfaceView.java
performs role similar to GLSurfaceView
. It interacts with the the native renderer to manage the display surface and handle touch events:public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { … … public MySurfaceView(Context context) { super(context); this.getHolder().addCallback(this); } … … public boolean onTouchEvent(final MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: float dx = x - mPreviousX; float dy = y - mPreviousY; mAngleX += dx * TOUCH_SCALE_FACTOR; mAngleY += dy * TOUCH_SCALE_FACTOR; naRequestRenderer(mAngleX, mAngleY); } mPreviousX = x; mPreviousY = y; return true; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { naSurfaceChanged(holder.getSurface()); } @Override public void surfaceCreated(SurfaceHolder holder) {} @Override public void surfaceDestroyed(SurfaceHolder holder) { naSurfaceDestroyed(); } }
jni
folder:android.opengl.GLSurfaceView.Renderer
. It sets up the EGL context, manages the display, and so on.renderAFrame
: It sets the event type, and then signals the rendering thread to handle the event:void Renderer::renderAFrame(float pAngleX, float pAngleY) { pthread_mutex_lock(&mMutex); mAngleX = pAngleX; mAngleY = pAngleY; mRendererEvent = RTE_DRAW_FRAME; pthread_mutex_unlock(&mMutex); pthread_cond_signal(&mCondVar); }
renderThreadRun
: It runs in a separate thread to handle various events, including surface change, draw a frame, and so on:void Renderer::renderThreadRun() { bool ifRendering = true; while (ifRendering) { pthread_mutex_lock(&mMutex); pthread_cond_wait(&mCondVar, &mMutex); switch (mRendererEvent) { … … case RTE_DRAW_FRAME: mRendererEvent = RTE_NONE; pthread_mutex_unlock(&mMutex); if (EGL_NO_DISPLAY!=mDisplay) { naDrawGraphics(mAngleX, mAngleY); eglSwapBuffers(mDisplay, mSurface); } } break; …… } } }
initDisplay
: It sets up the EGL context:bool Renderer::initDisplay() { const EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_NONE}; EGLint width, height, format; EGLint numConfigs; EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); eglChooseConfig(display, attribs, &config, 1, &numConfigs); eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(mWindow, 0, 0, format); surface = eglCreateWindowSurface(display, config, mWindow, NULL); context = eglCreateContext(display, config, NULL, NULL); if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { return -1; } eglQuerySurface(display, surface, EGL_WIDTH, &width); eglQuerySurface(display, surface, EGL_HEIGHT, &height); … ... }
EGLDemo.cpp
: It registers the native methods and wraps the native code. The following two methods are used:naSurfaceChanged
: It gets the native window
associated with a Java Surface
object and initializes EGL and OpenGL:
void naSurfaceChanged(JNIEnv* env, jclass clazz, jobject pSurface) { gWindow = ANativeWindow_fromSurface(env, pSurface); gRenderer->initEGLAndOpenGL1x(gWindow); }
naRequestRenderer
: It renders a frame, which is
called by the touch
event handler in MySurfaceView
:
void naRequestRenderer(JNIEnv* env, jclass clazz, float pAngleX, float pAngleY) { gRenderer->renderAFrame(pAngleX, pAngleY); }
Android.mk
file under the jni
folder with the following content:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := EGLDemo LOCAL_SRC_FILES := Cube.cpp OldRenderMethods.cpp Renderer.cpp EGLDemo.cpp LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM include $(BUILD_SHARED_LIBRARY)
EGL is an interface between OpenGL ES and the underlying native window system. According to Khronos EGL web page (http://www.khronos.org/egl), graphics context management, surface binding, and rendering synchronization for rendering with other Khronos 2D and 3D APIs, including OpenGL ES are handled by it.
EGL is a cross-platform API widely used in embedded systems, including Android and iPhone (the EGL implementation from Apple is called EAGL). Many desktop platforms also support EGL. Different implementations may not be 100 percent compatible, but the porting effort will usually not be substantial for the EGL code.
The following steps describe how to set up and manipulate EGL and its integration with OpenGL:
eglGetDisplay
: It obtains the EGL display connection for the native display. If the input argument is EGL_DEFAULT_DISPLAY
, a default display connection is returned.eglInitialize
: It initializes an EGL display connection obtained by eglGetDisplay
.eglChooseConfig
.eglChooseConfig
returns a list of EGL frame buffer configurations that match the requirements
specified by the attrib_list
argument. The attribute is an array with pairs of attributes and corresponding desired values, and it is terminated by EGL_NONE
. In our code, we simply specify EGL_SURFACE_TYPE
as EGL_WINDOW_BIT
, and color components sizes as 8 bit.
eglCreateWindowSurface
.eglCreateWindowSurface
, given the EGL display connection, the EGL frame buffer configuration and native window returns a new EGL window surface.
In our code, we start from SurfaceView
and pass its associated android.view.Surface
value to the native code. In the native code, we obtain its native window, and finally create the EGL window surface for OpenGL drawing.
eglCreateContext
and eglMakeCurrent
.eglSwapBuffers
call.eglSwapBuffers
posts the EGL surface color buffer to a native window. This effectively displays the drawing content on the screen.
EGL internally maintains two buffers. The content of the front buffer is displayed, while the drawing can be done on the back buffer. At the time we decided to display the new drawing, we swap the two buffers.
eglMakeCurrent
with EGL_NO_SURFACE
and EGL_NO_CONTEXT
releases the current contexteglDestroySurface
destroys an EGL surfaceeglTerminate
terminates the EGL display connectionOur code uses the Android native window management API calls to obtain a native window and configure it. The following methods are called:
ANativeWindow_fromSurface
: It returns a native window associated with the Java surface object. The returned reference should be passed to ANativeWindow_release
to ensure there's no leaking.ANativeWindow_setBuffersGeometry
: It sets the size and format of window buffers. In our code, we specified width and height as 0
, in which case the window's base value will be used.Note that we'll need to link to the Android library in the Android.mk
file (LOCAL_LDLIBS := -landroid
), because it is a part of the Android native application API, which we will cover more in the next chapter.
The renderer runs an event loop in a separate thread. We used the
POSIX thread (pthreads
) calls to create a native thread, synchronize it with the main thread, and so on. We'll cover pthread
in detail in Chapter 6, Android NDK Multithreading.