はじめに
NDK で NativeActivity から OpenGL ES 2.0 使って何かしようと思うと、EGL で初期化する処理を書いたりと、Java で GLSurfaceView 使うよりも手間がかかります。動作させるまで結構時間かかったのでまとめておきます。
三角形の描画
android-ndk/samples/native-activity と android-ndk/samples/hello-gl2 を参考にします。native-activity は glClearColor をセンサ/時間に応じて変えるだけのものですが、EGL の初期化手順が載っています。hello-gl2 は初期化は Java で行っているのですが、JNI を通じて C++ で書いた GL ES 2.0 の API 利用部分を呼び出しているコードが書いており、GL ES 2.0 部分を参考にすることができます。これらを組み合わせて書いてみます。なお、エラー処理などは見た目が冗長になるので省いています。
main.cpp
#include <EGL/egl.h> #include <GLES/gl.h> #include <GLES2/gl2.h> #include <android/log.h> #include <android_native_app_glue.h> #define LOG_TAG ("gles2ndtest") #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) struct engine { android_app* app; EGLDisplay display; EGLSurface surface; }; const char gVertexShader[] = "attribute vec4 vPosition;\n" "void main() {\n" " gl_Position = vPosition;\n" "}\n"; const char gFragmentShader[] = "precision mediump float;\n" "void main() {\n" " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" "}\n"; GLuint loadShader(GLenum shaderType, const char* pSource) { GLuint shader = glCreateShader(shaderType); glShaderSource(shader, 1, &pSource, nullptr); glCompileShader(shader); GLint compiled; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); return shader; } GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) { GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource); GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource); GLuint program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, pixelShader); glLinkProgram(program); GLint linkStatus = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); return program; } int init(engine* e) { const EGLint attribs[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); EGLConfig config; EGLint numConfigs; eglChooseConfig(display, attribs, &config, 1, &numConfigs); EGLint format; eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(e->app->window, 0, 0, format); EGLSurface surface = eglCreateWindowSurface(display, config, e->app->window, nullptr); EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { return -1; } EGLint w, h; eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); e->display = display; e->surface = surface; glViewport(0, 0, w, h); } // 描画 void draw(engine* e) { GLuint gProgram = createProgram(gVertexShader, gFragmentShader); GLuint gvPositionHandle = glGetAttribLocation(gProgram, "vPosition"); const GLfloat vertices[] = { 0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f }; glClearColor(0.3f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(gProgram); glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, vertices); glEnableVertexAttribArray(gvPositionHandle); glDrawArrays(GL_TRIANGLES, 0, 3); eglSwapBuffers(e->display, e->surface); } void android_main(android_app* state) { app_dummy(); engine e; state->userData = &e; state->onAppCmd = [](android_app* app, int32_t cmd) { auto e = static_cast<engine*>(app->userData); switch (cmd) { case APP_CMD_INIT_WINDOW: init(e); draw(e); break; } }; e.app = state; while (1) { int ident, events; android_poll_source* source; while ((ident=ALooper_pollAll(0, nullptr, &events, (void**)&source)) >= 0) { if (source != nullptr) { source->process(state, source); } if (state->destroyRequested != 0) { return; } } } }
Application.mk
APP_PLATFORM := android-10 APP_OPTIM := debug APP_ABI := armeabi armeabi-v7a
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := gles2ndktest LOCAL_CFLAGS := -std=c++11 LOCAL_SRC_FILES := main.cpp LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM -lGLESv2 LOCAL_STATIC_LIBRARIES := android_native_app_glue include $(BUILD_SHARED_LIBRARY) $(call import-module,android/native_app_glue)
AndroidManifest.xml
Uses Feature から Gl es version を 0x00020000 にします。
おまけ
Vim を使っている人は quickrun から ndk-build を設定すると、エラーを吐いているファイルの行へ quickfix から飛べて便利です。
let g:quickrun_config['c/ndk-build'] = { \ 'exec' : '%c', \ 'command' : 'ndk-build', \ 'runner' : 'vimproc', \ } let g:quickrun_config['cpp/ndk-build'] = { \ 'exec' : '%c', \ 'command' : 'ndk-build', \ 'runner' : 'vimproc', \ }
おわりに
次はテクスチャ読み込んで表示するところやります。
参考
- 作者: 山下武志
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/07/21
- メディア: 大型本
- 購入: 3人 クリック: 71回
- この商品を含むブログ (9件) を見る
- 作者: 出村成和
- 出版社/メーカー: 秀和システム
- 発売日: 2011/07
- メディア: 単行本
- クリック: 83回
- この商品を含むブログ (12件) を見る