#include <memory>
#include <string>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
...略...
template<typename FunGetIv, typename FunGetLog>
std::string getGLLogStr(GLuint obj, FunGetIv funGetIv, FunGetLog funGetLog)
{
GLint len = 0;
funGetIv(obj, GL_INFO_LOG_LENGTH, &len);
if(len > 1){
std::unique_ptr<char[]> infoLog(new char[len]);
funGetLog(obj, len, nullptr, infoLog.get());
return std::string(infoLog.get());
}
else{
return std::string();
}
}
std::string getGLShaderLogInfo(GLuint obj)
{
return getGLLogStr(obj, glGetShaderiv, glGetShaderInfoLog);
}
std::string getGLProgramLogInfo(GLuint obj)
{
return getGLLogStr(obj, glGetProgramiv, glGetProgramInfoLog);
}
GLuint loadShader(GLenum type, const char *shaderSrc)
{
GLuint shader = glCreateShader(type);
if(shader == 0){
return 0;
}
glShaderSource(shader, 1, &shaderSrc, nullptr);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if(!compiled){
LOGI("glCompileShader failed.\n Error:(%s)\n", getGLShaderLogInfo(shader).c_str());
glDeleteShader(shader);
return 0;
}
return shader;
}
class GLEnvironment
{
EGLDisplay display_;
EGLSurface surface_;
EGLConfig config_;
EGLint screenWidth_;
EGLint screenHeight_;
EGLContext context_;
public:
GLEnvironment()
: display_(EGL_NO_DISPLAY),
surface_(EGL_NO_SURFACE),
config_(nullptr),
screenWidth_(), screenHeight_(),
context_(EGL_NO_CONTEXT)
{}
~GLEnvironment()
{
destroy();
}
GLEnvironment(const GLEnvironment &) = delete;
GLEnvironment &operator=(const GLEnvironment &) = delete;
bool create(ANativeWindow *window)
{
return initContext(window);
}
void destroy()
{
disconnectDisplay();
}
bool recreate(ANativeWindow *window)
{
destroy();
return create(window);
}
static const bool preserveEGLContextOnPause = true;
bool initWindow(ANativeWindow *window)
{
LOGI("initWindow");
if(preserveEGLContextOnPause && hasDisplay() && !hasSurface() && hasContext() && isContextInitialized()){
LOGI("Trying to resume");
if(createSurface(window, config_) && makeContextCurrent(window)){
LOGI("resume succeeded");
return true;
}
LOGI("resume failed");
}
return recreate(window);
}
void termWindow()
{
if(!preserveEGLContextOnPause){
destroyContext();
}
destroySurface();
}
bool presentFrame(ANativeWindow *window)
{
if(!isContextInitialized()){
LOGE("Context not initialized");
return false;
}
drawFrame();
if(!swap()){
recreate(window);
drawFrame();
if(!swap()){
LOGE("Swap failed twice");
return false;
}
}
return true;
}
private:
bool swap()
{
if(hasSurface()){
LOGI("swap");
if(!eglSwapBuffers(display_, surface_)){
LOGI("eglSwapBuffers failed");
return false;
}
}
return true;
}
virtual void drawFrame(){}
private:
bool hasDisplay() const {return display_ != EGL_NO_DISPLAY;}
bool connectDisplay()
{
if(display_ == EGL_NO_DISPLAY){
const EGLDisplay uninitializedDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(uninitializedDisplay == EGL_NO_DISPLAY){
LOGE("eglGetDisplay failed");
return false;
}
if(!eglInitialize(uninitializedDisplay, 0, 0)){
LOGE("eglInitialize failed");
return false;
}
display_ = uninitializedDisplay;
}
return true;
}
void disconnectDisplay()
{
destroyContext();
destroySurface();
if(display_ != EGL_NO_DISPLAY){
LOGI("eglTerminate");
eglTerminate(display_);
display_ = EGL_NO_DISPLAY;
}
}
private:
static EGLConfig chooseConfig(EGLDisplay display)
{
struct DesiredConfig
{
int depth;
DesiredConfig(int depth):depth(depth){}
};
const DesiredConfig desiredConfigs[] = {DesiredConfig(24), DesiredConfig(16)};
const DesiredConfig * const dcEnd = desiredConfigs + sizeof(desiredConfigs)/sizeof(desiredConfigs[0]);
const DesiredConfig *dc;
EGLConfig config = nullptr;
for(dc = desiredConfigs; dc != dcEnd; ++dc){
const EGLint desiredAttribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, dc->depth,
EGL_NONE };
EGLint numConfigs;
if(!eglChooseConfig(display, desiredAttribs, &config, 1, &numConfigs)){
LOGE("eglChooseConfig failed");
return nullptr;
}
if(numConfigs == 1){
break;
}
}
if(config == nullptr){
LOGE("no EGL config");
return nullptr;
}
LOGI("match config depth=%d", dc->depth);
return config;
}
private:
bool hasSurface() const { return surface_ != EGL_NO_SURFACE;}
bool createSurface(ANativeWindow *window, EGLConfig desiredConfig = nullptr)
{
if(!connectDisplay()){
return false;
}
if(surface_ == EGL_NO_SURFACE){
const EGLConfig config = desiredConfig ? desiredConfig : chooseConfig(display_);
if(!config){
LOGE("chooseConfig failed");
return false;
}
EGLint format;
eglGetConfigAttrib(display_, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(window, 0, 0, format);
surface_ = eglCreateWindowSurface(display_, config, window, nullptr);
if(surface_ == EGL_NO_SURFACE){
LOGE("eglCreateWindowSurface failed. error=%d", eglGetError());
return false;
}
config_ = config;
eglQuerySurface(display_, surface_, EGL_WIDTH, &screenWidth_);
eglQuerySurface(display_, surface_, EGL_HEIGHT, &screenHeight_);
LOGI("screen size %d x %d", screenWidth_, screenHeight_);
}
return true;
}
void destroySurface()
{
if(surface_ != EGL_NO_SURFACE){
LOGI("eglDestroySurface(window surface)");
eglDestroySurface(display_, surface_);
surface_ = EGL_NO_SURFACE;
}
}
public:
int getScreenWidth() const {return screenWidth_;}
int getScreenHeight() const {return screenHeight_;}
private:
bool hasContext() const{ return context_ != EGL_NO_CONTEXT;}
bool createContext(ANativeWindow *window)
{
if(!createSurface(window)){
return false;
}
if(context_ == EGL_NO_CONTEXT){
const EGLint attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE };
context_ = eglCreateContext(display_, config_, nullptr, attribs);
if(context_ == EGL_NO_CONTEXT){
LOGE("eglCreateContext failed");
return false;
}
}
return true;
}
void destroyContext()
{
termContext();
if(context_ != EGL_NO_CONTEXT){
LOGI("eglMakeCurrent null");
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
LOGI("eglDestroyContext");
eglDestroyContext(display_, context_);
context_ = EGL_NO_CONTEXT;
}
}
bool makeContextCurrent(ANativeWindow *window)
{
if(!createSurface(window)){
return false;
}
if(!createContext(window)){
return false;
}
LOGI("eglMakeCurrent");
if(!eglMakeCurrent(display_, surface_, surface_, context_)){
LOGE( "eglMakeCurrent failed" );
return false;
}
return true;
}
void detachCurrentContext()
{
if(display_ != EGL_NO_DISPLAY){
LOGI("detachCurrentContext");
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
}
bool isContextCurrent()
{
return context_ != EGL_NO_CONTEXT && eglGetCurrentContext() == context_;
}
bool contextInitialized_ = false;
protected:
bool isContextInitialized() const {return contextInitialized_;}
private:
bool initContext(ANativeWindow *window)
{
if(!makeContextCurrent(window)){
return false;
}
if(!contextInitialized_){
if(!loadResources()){
return false;
}
initializeContextState();
contextInitialized_ = true;
}
return true;
}
void termContext()
{
if(contextInitialized_){
unloadResources();
contextInitialized_ = false;
}
}
virtual bool loadResources(){return true;}
virtual void unloadResources(){}
virtual void initializeContextState(){}
};
class ThisAppGraphics : public GLEnvironment
{
GLuint vertexShader_;
GLuint fragmentShader_;
GLuint programObject_;
public:
ThisAppGraphics()
: GLEnvironment(),
vertexShader_(0),
fragmentShader_(0),
programObject_(0)
{
}
private:
virtual bool loadResources() override
{
LOGI("loadResources");
const char vShaderStr[] =
"attribute vec4 vPosition;"
"void main(){"
" gl_Position = vPosition;"
"}";
const char fShaderStr[] =
"precision mediump float;"
"void main(){"
" gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);"
"}";
vertexShader_ = loadShader(GL_VERTEX_SHADER, vShaderStr);
fragmentShader_ = loadShader(GL_FRAGMENT_SHADER, fShaderStr);
programObject_ = glCreateProgram();
glAttachShader(programObject_, vertexShader_);
glAttachShader(programObject_, fragmentShader_);
glBindAttribLocation(programObject_, 0, "vPosition");
glLinkProgram(programObject_);
GLint linked = 0;
glGetProgramiv(programObject_, GL_LINK_STATUS, &linked);
if(!linked){
LOGI("glLinkProgram failed.\n Error:(%s)\n", getGLProgramLogInfo(programObject_).c_str());
return false;
}
return true;
}
virtual void unloadResources() override
{
LOGI("unloadResources");
if(programObject_){
glDeleteProgram(programObject_);
programObject_ = 0;
}
if(vertexShader_){
glDeleteShader(vertexShader_);
vertexShader_ = 0;
}
if(fragmentShader_){
glDeleteShader(fragmentShader_);
fragmentShader_ = 0;
}
}
virtual void initializeContextState() override
{
glEnable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glViewport(0, 0, getScreenWidth(), getScreenHeight());
}
virtual void drawFrame() override
{
LOGI("drawFrame");
GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(programObject_);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
};
class ThisApp
{
...略...
ThisAppGraphics gl_;
...略...
void handleCmd(int32_t cmd)
{
switch(cmd){
case APP_CMD_INIT_WINDOW:
LOGI("handleCmd(APP_CMD_INIT_WINDOW)");
if(app_->window){
gl_.initWindow(app_->window);
gl_.presentFrame(app_->window);
}
break;
case APP_CMD_TERM_WINDOW:
LOGI("handleCmd(APP_CMD_TERM_WINDOW)");
gl_.termWindow();
break;
...略...
他のプロジェクトでも使い回せるようなコードはGLEnvironmentクラスに収め、このプロジェクトに固有な描画コードはThisAppGraphicsクラスに収めてみました。ThisAppクラスからはapp_->windowが有効になったときにinitWindow()やpresentFrame()を、app_->windowが無効になったときにtermWindow()を呼び出すだけです。