Configure graphics with OpenGL ES

To draw objects and sprites in your game, you will need to configure the display, surface and context variables, set up rendering in your game loop, and draw each scene and object.

There are two ways to draw images to the screen for a C or C++ game, namely with OpenGL ES, or Vulkan.

Before you get started

If you haven't already done so, set up a GameActivity object in your Android project.

Set up OpenGL ES variables

  1. You will need a display, surface, context, and config to render your game. Add the following OpenGL ES variables to your game engine's header file:

    classNativeEngine{
    //...
    private:
    EGLDisplaymEglDisplay;
    EGLSurfacemEglSurface;
    EGLContextmEglContext;
    EGLConfigmEglConfig;
    boolmHasFocus,mIsVisible,mHasWindow;
    boolmHasGLObjects;
    boolmIsFirstFrame;
    intmSurfWidth,mSurfHeight;
    }
    
  2. In the constructor for your game engine, initialize the default values for the variables.

    NativeEngine::NativeEngine(structandroid_app*app){
    //...
    mEglDisplay=EGL_NO_DISPLAY;
    mEglSurface=EGL_NO_SURFACE;
    mEglContext=EGL_NO_CONTEXT;
    mEglConfig=0;
    mHasFocus=mIsVisible=mHasWindow=false;
    mHasGLObjects=false;
    mIsFirstFrame=true;
    mSurfWidth=mSurfHeight=0;
    }
    
  3. Initialize the display to render.

    boolNativeEngine::InitDisplay(){
    if(mEglDisplay!=EGL_NO_DISPLAY){
    returntrue;
    }
    mEglDisplay=eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if(EGL_FALSE==eglInitialize(mEglDisplay,0,0)){
    LOGE("NativeEngine: failed to init display, error %d",eglGetError());
    returnfalse;
    }
    returntrue;
    }
    
  4. The surface can be an off-screen buffer (pbuffer) allocated by EGL, or a window allocated by the Android OS. Initialize this surface:

    boolNativeEngine::InitSurface(){
    ASSERT(mEglDisplay!=EGL_NO_DISPLAY);
    if(mEglSurface!=EGL_NO_SURFACE){
    returntrue;
    }
    EGLintnumConfigs;
    constEGLintattribs[]={
    EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT,// request OpenGL ES 2.0
    EGL_SURFACE_TYPE,EGL_WINDOW_BIT,
    EGL_BLUE_SIZE,8,
    EGL_GREEN_SIZE,8,
    EGL_RED_SIZE,8,
    EGL_DEPTH_SIZE,16,
    EGL_NONE
    };
    // Pick the first EGLConfig that matches.
    eglChooseConfig(mEglDisplay,attribs,&mEglConfig,1,&numConfigs);
    mEglSurface=eglCreateWindowSurface(mEglDisplay,mEglConfig,mApp->window,
    NULL);
    if(mEglSurface==EGL_NO_SURFACE){
    LOGE("Failed to create EGL surface, EGL error %d",eglGetError());
    returnfalse;
    }
    returntrue;
    }
    
  5. Initialize the rendering context. This example creates an OpenGL ES 2.0 context:

    boolNativeEngine::InitContext(){
    ASSERT(mEglDisplay!=EGL_NO_DISPLAY);
    if(mEglContext!=EGL_NO_CONTEXT){
    returntrue;
    }
    // OpenGL ES 2.0
    EGLintattribList[]={EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE};
    mEglContext=eglCreateContext(mEglDisplay,mEglConfig,NULL,attribList);
    if(mEglContext==EGL_NO_CONTEXT){
    LOGE("Failed to create EGL context, EGL error %d",eglGetError());
    returnfalse;
    }
    returntrue;
    }
    
  6. Configure your OpenGL ES settings before drawing. This example is executed at the beginning of every frame. It enables depth testing, sets the clear color to black, and clears the color and depth buffers.

    voidNativeEngine::ConfigureOpenGL(){
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
    }
    

Render with the game loop

  1. The game loop renders a frame and repeats indefinitely until the user quits. Between frames, your game may:

    • Process events such as input, audio output, and networking events.

    • Update the game logic and user interface.

    • Render a frame to the display.

    To render a frame to the display, the DoFrame method is called indefinitely in the game loop:

    voidNativeEngine::GameLoop(){
    // Loop indefinitely.
    while(1){
    intevents;
    structandroid_poll_source*source;
    // If not animating, block until we get an event.
    while((ALooper_pollAll(IsAnimating()?0:-1,NULL,&events,
    (void**)&source))>=0){
    // Process events.
    ...
    }
    // Render a frame.
    if(IsAnimating()){
    DoFrame();
    }
    }
    }
    
  2. In the DoFrame method, query the current surface dimensions, request SceneManager to render a frame, and swap the display buffers.

    voidNativeEngine::DoFrame(){
    ...
    // Query the current surface dimension.
    intwidth,height;
    eglQuerySurface(mEglDisplay,mEglSurface,EGL_WIDTH,&width);
    eglQuerySurface(mEglDisplay,mEglSurface,EGL_HEIGHT,&height);
    // Handle dimension changes.
    SceneManager*mgr=SceneManager::GetInstance();
    if(width!=mSurfWidth||height!=mSurfHeight){
    mSurfWidth=width;
    mSurfHeight=height;
    mgr->SetScreenSize(mSurfWidth,mSurfHeight);
    glViewport(0,0,mSurfWidth,mSurfHeight);
    }
    ...
    // Render scenes and objects.
    mgr->DoFrame();
    // Swap buffers.
    if(EGL_FALSE==eglSwapBuffers(mEglDisplay,mEglSurface)){
    HandleEglError(eglGetError());
    }
    }
    

Render scenes and objects

  1. The game loop processes a hierarchy of visible scenes and objects to render. In the Endless Tunnel example, a SceneManager keeps track of multiple scenes, with only one scene active at a time. In this example, the current scene is rendered:

    voidSceneManager::DoFrame(){
    if(mSceneToInstall){
    InstallScene(mSceneToInstall);
    mSceneToInstall=NULL;
    }
    if(mHasGraphics && mCurScene){
    mCurScene->DoFrame();
    }
    }
    
  2. Depending on your game, a scene may contain background, text, sprites and game objects. Render them in the order suitable for your game. This example renders the background, text, and widgets:

    voidUiScene::DoFrame(){
    // clear screen
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);
    RenderBackground();
    // Render the "Please Wait" sign and do nothing else
    if(mWaitScreen){
    SceneManager*mgr=SceneManager::GetInstance();
    mTextRenderer->SetFontScale(WAIT_SIGN_SCALE);
    mTextRenderer->SetColor(1.0f,1.0f,1.0f);
    mTextRenderer->RenderText(S_PLEASE_WAIT,mgr->GetScreenAspect()*0.5f,
    0.5f);
    glEnable(GL_DEPTH_TEST);
    return;
    }
    // Render all the widgets.
    for(inti=0;i < mWidgetCount;++i){
    mWidgets[i]->Render(mTrivialShader,mTextRenderer,mShapeRenderer,
    (mFocusWidget < 0)?UiWidget::FOCUS_NOT_APPLICABLE:
    (mFocusWidget==i)?UiWidget::FOCUS_YES:UiWidget::FOCUS_NO,tf);
    }
    glEnable(GL_DEPTH_TEST);
    }
    

Resources

Read the following for more information about OpenGL ES and Vulkan:

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026年02月26日 UTC.