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;}
표면은 EGL에서 할당한 화면 밖 버퍼(pbuffer) 또는 Android OS에서 할당한 창일 수 있습니다. 표면 초기화는 다음과 같습니다.
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.0EGL_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;}
프레임을 디스플레이에 렌더링하기 위해 DoFrame 메서드는 게임 루프에서 무기한 호출됩니다.
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();}}}
DoFrame 메서드에서 현재 표면 크기를 쿼리하고 SceneManager를 지정하여 프레임을 렌더링한 다음 디스플레이 버퍼를 교환합니다.
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());}}
장면 및 객체 렌더링
게임 루프는 보이는 장면과 객체의 계층 구조를 처리하여 렌더링합니다.
Endless Tunnel 예시에서 SceneManager는 한 번에 하나의 장면만 활성화하여 여러 장면을 추적합니다. 이 예에서는 현재 장면이 렌더링됩니다.
게임에 따라 장면에 배경, 텍스트, 스프라이트, 게임 객체가 포함될 수 있습니다. 게임에 적합한 순서대로 렌더링합니다. 이 예에서는 배경, 텍스트, 위젯을 렌더링합니다.
voidUiScene::DoFrame(){// clear screenglClearColor(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 elseif(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);}
Android 게임 루프 이해 - 프레임 속도 지정, 대기열 버퍼, VSYNC 콜백 처리, 스레드 관리 방법을 알아봅니다.
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-07-27(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-07-27(UTC)"],[],[],null,["# Configure graphics with OpenGL ES\n\nTo draw objects and sprites in your game, you will need to configure the\ndisplay, surface and context variables, set up rendering in your game loop, and\ndraw each scene and object.\n\nThere are two ways to draw images to the screen for a C or C++ game, namely with\n[OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl), or\n[Vulkan](/ndk/guides/graphics/getting-started).\n\n- [OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl) is part of the [Open Graphics\n Library (OpenGL®)](https://www.khronos.org/opengles/) specification\n intended for mobile devices such as Android. Learn how to configure OpenGL ES\n for your game in this topic.\n\n- If you use Vulkan for your game, read the\n [Getting started with Vulkan](/ndk/guides/graphics/getting-started)\n guide.\n\n| **Note:** The code in this topic is based on the [Endless Tunnel](https://github.com/android/ndk-samples/tree/master/endless-tunnel) sample, where details may differ for your game. Understand and adapt these concepts for your specific use case.\n\nBefore you get started\n----------------------\n\nIf you haven't already done so,\n[set up a GameActivity object](/games/agdk/game-activity) in your\nAndroid project.\n\nSet up OpenGL ES variables\n--------------------------\n\n1. You will need a [display](/reference/android/opengl/EGLDisplay),\n [surface](/reference/android/opengl/EGLSurface),\n [context](/reference/android/opengl/EGLContext), and\n [config](/reference/android/opengl/EGLConfig) to render your game. Add the\n following OpenGL ES variables to your game engine's header file:\n\n class NativeEngine {\n //...\n private:\n EGLDisplay mEglDisplay;\n EGLSurface mEglSurface;\n EGLContext mEglContext;\n EGLConfig mEglConfig;\n\n bool mHasFocus, mIsVisible, mHasWindow;\n bool mHasGLObjects;\n bool mIsFirstFrame;\n\n int mSurfWidth, mSurfHeight;\n }\n\n2. In the constructor for your game engine, initialize the default values for\n the variables.\n\n NativeEngine::NativeEngine(struct android_app *app) {\n //...\n mEglDisplay = EGL_NO_DISPLAY;\n mEglSurface = EGL_NO_SURFACE;\n mEglContext = EGL_NO_CONTEXT;\n mEglConfig = 0;\n\n mHasFocus = mIsVisible = mHasWindow = false;\n mHasGLObjects = false;\n mIsFirstFrame = true;\n\n mSurfWidth = mSurfHeight = 0;\n }\n\n3. Initialize the display to render.\n\n bool NativeEngine::InitDisplay() {\n if (mEglDisplay != EGL_NO_DISPLAY) {\n return true;\n }\n\n mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n if (EGL_FALSE == eglInitialize(mEglDisplay, 0, 0)) {\n LOGE(\"NativeEngine: failed to init display, error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n4. The surface can be an off-screen buffer (pbuffer) allocated by EGL, or a\n window allocated by the Android OS. Initialize this surface:\n\n bool NativeEngine::InitSurface() {\n ASSERT(mEglDisplay != EGL_NO_DISPLAY);\n if (mEglSurface != EGL_NO_SURFACE) {\n return true;\n }\n\n EGLint numConfigs;\n const EGLint attribs[] = {\n EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // request OpenGL ES 2.0\n EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n EGL_BLUE_SIZE, 8,\n EGL_GREEN_SIZE, 8,\n EGL_RED_SIZE, 8,\n EGL_DEPTH_SIZE, 16,\n EGL_NONE\n };\n\n // Pick the first EGLConfig that matches.\n eglChooseConfig(mEglDisplay, attribs, &mEglConfig, 1, &numConfigs);\n mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, mApp-\u003ewindow,\n NULL);\n if (mEglSurface == EGL_NO_SURFACE) {\n LOGE(\"Failed to create EGL surface, EGL error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n5. Initialize the rendering context. This example creates an\n [OpenGL ES 2.0](/reference/android/opengl/GLES20) context:\n\n bool NativeEngine::InitContext() {\n ASSERT(mEglDisplay != EGL_NO_DISPLAY);\n if (mEglContext != EGL_NO_CONTEXT) {\n return true;\n }\n\n // OpenGL ES 2.0\n EGLint attribList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };\n mEglContext = eglCreateContext(mEglDisplay, mEglConfig, NULL, attribList);\n if (mEglContext == EGL_NO_CONTEXT) {\n LOGE(\"Failed to create EGL context, EGL error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n6. Configure your OpenGL ES settings before drawing. This example is executed at\n the beginning of every frame. It enables depth testing, sets the clear color to\n black, and clears the color and depth buffers.\n\n void NativeEngine::ConfigureOpenGL() {\n glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n glEnable(GL_DEPTH_TEST);\n glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);\n }\n\nRender with the game loop\n-------------------------\n\n1. The game loop renders a frame and repeats indefinitely until the user quits.\n Between frames, your game may:\n\n - [Process events](/games/agdk/game-activity/get-started#handle-events) such as\n input, [audio output](/games/sdk/oboe), and networking events.\n\n - Update the game logic and user interface.\n\n - Render a frame to the display.\n\n To render a frame to the display, the `DoFrame` method is called\n indefinitely in the game loop: \n\n void NativeEngine::GameLoop() {\n // Loop indefinitely.\n while (1) {\n int events;\n struct android_poll_source* source;\n\n // If not animating, block until we get an event.\n while ((ALooper_pollAll(IsAnimating() ? 0 : -1, NULL, &events,\n (void **) &source)) \u003e= 0) {\n // Process events.\n ...\n }\n\n // Render a frame.\n if (IsAnimating()) {\n DoFrame();\n }\n }\n }\n\n2. In the `DoFrame` method, query the current surface dimensions, request\n `SceneManager` to render a frame, and swap the display buffers.\n\n void NativeEngine::DoFrame() {\n ...\n // Query the current surface dimension.\n int width, height;\n eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width);\n eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height);\n\n // Handle dimension changes.\n SceneManager *mgr = SceneManager::GetInstance();\n if (width != mSurfWidth || height != mSurfHeight) {\n mSurfWidth = width;\n mSurfHeight = height;\n mgr-\u003eSetScreenSize(mSurfWidth, mSurfHeight);\n glViewport(0, 0, mSurfWidth, mSurfHeight);\n }\n ...\n // Render scenes and objects.\n mgr-\u003eDoFrame();\n\n // Swap buffers.\n if (EGL_FALSE == eglSwapBuffers(mEglDisplay, mEglSurface)) {\n HandleEglError(eglGetError());\n }\n }\n\nRender scenes and objects\n-------------------------\n\n1. The game loop processes a hierarchy of visible scenes and objects to render.\n In the Endless Tunnel example, a `SceneManager` keeps track of multiple scenes,\n with only one scene active at a time. In this example, the current scene is\n rendered:\n\n void SceneManager::DoFrame() {\n if (mSceneToInstall) {\n InstallScene(mSceneToInstall);\n mSceneToInstall = NULL;\n }\n\n if (mHasGraphics && mCurScene) {\n mCurScene-\u003eDoFrame();\n }\n }\n\n2. Depending on your game, a scene may contain background, text, sprites and\n game objects. Render them in the order suitable for your game. This example\n renders the background, text, and widgets:\n\n void UiScene::DoFrame() {\n // clear screen\n glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n glDisable(GL_DEPTH_TEST);\n\n RenderBackground();\n\n // Render the \"Please Wait\" sign and do nothing else\n if (mWaitScreen) {\n SceneManager *mgr = SceneManager::GetInstance();\n mTextRenderer-\u003eSetFontScale(WAIT_SIGN_SCALE);\n mTextRenderer-\u003eSetColor(1.0f, 1.0f, 1.0f);\n mTextRenderer-\u003eRenderText(S_PLEASE_WAIT, mgr-\u003eGetScreenAspect() * 0.5f,\n 0.5f);\n glEnable(GL_DEPTH_TEST);\n return;\n }\n\n // Render all the widgets.\n for (int i = 0; i \u003c mWidgetCount; ++i) {\n mWidgets[i]-\u003eRender(mTrivialShader, mTextRenderer, mShapeRenderer,\n (mFocusWidget \u003c 0) ? UiWidget::FOCUS_NOT_APPLICABLE :\n (mFocusWidget == i) ? UiWidget::FOCUS_YES : UiWidget::FOCUS_NO,tf);\n }\n glEnable(GL_DEPTH_TEST);\n }\n\nResources\n---------\n\nRead the following for more information about OpenGL ES and Vulkan:\n\n- [OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl) - Images and graphics in Android.\n\n- [OpenGL ES](https://source.android.com/devices/graphics/arch-egl-opengl) -\n Overview in Android Source.\n\n- [Vulkan](/ndk/guides/graphics/getting-started) - Getting started in NDK.\n\n- [Vulkan](https://source.android.com/devices/graphics/arch-vulkan) - Overview\n in Android Source.\n\n- [Understand Android game loops](/games/develop/gameloops) - learn to pace\n frames, queue buffers, handle VSYNC callbacks, and manage threads."]]