凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

NativeActivity から OpenGL ES 2.0 で三角形を描画する

はじめに

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 にします。

結果


エラー処理とかは省いてます。もとの sample を参考にしてみて下さい。

おまけ

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',
\ }

おわりに

次はテクスチャ読み込んで表示するところやります。

参考

初めてのOpenGL ES

初めてのOpenGL ES

Android NDKネイティブプログラミング

Android NDKネイティブプログラミング

第2版出ているのか…。