1. Link the log library
- Open the CMakeLists.txt file.
- Make the following changes:
cmake_minimum_required(VERSION 3.6.0) add_library(native-library SHARED main.cpp) find_library(opengl-lib GLESv3) find_library(log-lib log) target_link_libraries(native-library ${opengl-lib} ${log-lib})
- Save the file and click Sync Now.
2. Add the LOGE macro
- Open the main.cpp file.
- Add the following code just below the import statements:
#define LOG_TAG "native-library" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
From now on you can use the LOGE function to print errors to the console.
3. Create the vertex shader
- Open the main.cpp file.
- Add the following constant:
static const GLchar vertexShaderSource[] = "#version 310 es\n" "layout (location = 0) in vec3 pos;\n" "void main()\n" "{\n" "gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);\n" "}\n";
This is the source code of the vertex shader. Later you will compile it and attach it to the OpenGL program.
4. Create the fragment shader
- Add the following constant:
static const GLchar fragmentShaderSource[] = "#version 310 es\n" "precision mediump float;\n" "out vec4 color;\n" "void main()\n" "{\n" "color = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n";
This is the source code of the fragment shader. Later you will compile it and attach it to the OpenGL program.
5. Compile the shaders
- Add the following function:
GLuint loadShader(GLenum shaderType, const GLchar* shaderSource) { GLuint shader = glCreateShader(shaderType); if (shader) { glShaderSource(shader, 1, &shaderSource, NULL); glCompileShader(shader); GLint compileStatus = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); if (!compileStatus) { GLint infoLogLength = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); if (infoLogLength > 0) { GLchar* infoLogBuffer = (GLchar*) malloc(static_cast<size_t>(infoLogLength)); if (infoLogBuffer != NULL) { glGetShaderInfoLog(shader, infoLogLength, NULL, infoLogBuffer); LOGE("Could not compile the shader %d: %s", shaderType, infoLogBuffer); free(infoLogBuffer); } } glDeleteShader(shader); shader = 0; } } return shader; }
This function compiles the source code of the given shader. You will use it while creating the OpenGL program.
If the compilation is successful, it returns the handle of the shader. Otherwise it returns 0.
6. Create the OpenGL program
- Declare the following global variable, just after fragmentShaderSource:
GLuint program;
- Add the following function:
void createProgram() { program = 0; GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexShaderSource); GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentShaderSource); if (!vertexShader || !fragmentShader) { LOGE("Could not create the program: vertex or fragment shader failed to compile"); return; } program = glCreateProgram(); if (program) { glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); if (!linkProgram(program) || !validateProgram(program)) { LOGE("Could not create the program: program link or validation failed"); glDeleteProgram(program); program = 0; } } return; }
This function creates an OpenGL program, compiles the vertex and fragment shaders, attaches them to the program and finally links and validates the program.
If everything goes well, it returns the handle of the program. Otherwise it returns 0.
- Because you used the linkProgram and validateProgram functions before implementing them, add the following prototypes (after the import statements):
bool linkProgram(GLuint program); bool validateProgram(GLuint program);
- Provide the implementation of the prototypes:
bool linkProgram(GLuint program) { glLinkProgram(program); GLint linkStatus = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (!linkStatus) { GLint infoLogLength = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); if (infoLogLength > 0) { GLchar* infoLogBuffer = (GLchar*) malloc(static_cast<size_t >(infoLogLength)); if (infoLogBuffer != NULL) { glGetProgramInfoLog(program, infoLogLength, NULL, infoLogBuffer); LOGE("Could not link the program: %s", infoLogBuffer); free(infoLogBuffer); } } return false; } return true; }
This function links the given program and returns true on success or false otherwise.
bool validateProgram(GLuint program) { glValidateProgram(program); GLint validateStatus = GL_FALSE; glGetProgramiv(program, GL_VALIDATE_STATUS, &validateStatus); if (validateStatus != GL_TRUE) { GLint infoLogLength = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); if (infoLogLength) { GLchar* infoLogBuffer = (GLchar*) malloc(infoLogLength); if (infoLogBuffer) { glGetProgramInfoLog(program, infoLogLength, NULL, infoLogBuffer); LOGE("Could not validate the program: %s", infoLogBuffer); free(infoLogBuffer); } } return false; } return true; }
This function validates the given program and returns true if it is valid or false otherwise.
7. Prepare the drawing data
- Along with the program, declare two more global variables:
GLuint program, triangleVAO, triangleVBO;
triangleVAO and triangleVBO will serve as the handles of the VAO and VBO buffers respectively. You will create these buffers next.
- Add the following function:
void createTriangle() { GLfloat vertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f }; glGenVertexArrays(1, &triangleVAO); glBindVertexArray(triangleVAO); glGenBuffers(1, &triangleVBO); glBindBuffer(GL_ARRAY_BUFFER, triangleVBO); glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(GL_FLOAT), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); return; }
This function creates the VBA and VBO buffers for the triangle. It fills the VBO with the triangle’s vertices and sets it as the pos input of the vertex shader (location 0).
Each VAO corresponds to an object you want to draw and can have multiple VBO: the VAO is an array of VBO and each VBO holds the actual data.
8. Draw
- Make the following changes to the Java_dev_anastasioscho_glestriangle_NativeLibrary_nOnSurfaceCreated function:
extern "C" JNIEXPORT void JNICALL Java_dev_anastasioscho_glestriangle_NativeLibrary_nOnSurfaceCreated(JNIEnv * env, jobject obj) { glClearColor(0.0, 1.0, 0.0, 1.0); createProgram(); createTriangle(); return; }
This function gets called only once so its the perfect place to create the program and the triangle’s VBA and VBO buffers.
- Make the following changes to the Java_dev_anastasioscho_glestriangle_NativeLibrary_nOnDrawFrame function:
extern "C" JNIEXPORT void JNICALL Java_dev_anastasioscho_glestriangle_NativeLibrary_nOnDrawFrame(JNIEnv * env, jobject obj) { glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); glBindVertexArray(triangleVAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); glUseProgram(0); return; }
This function gets called on every frame so its the right place to do your drawing.
As you can see you can have multiple programs and choose which one to use. You also choose which VAO to draw.
Leave a Reply