Android NDK OpenGL介紹

現在來介紹Android NDK OpenGL 的 code,

這段code是出在這裡: http://blog.csdn.net/wangchenggggdn/article/details/8896453 本來想從ios的code來porting,不過人家有類似的android code,就直接拿來修改比較快。

#include <jni.h> #include <android/log.h>

#include <GLES2/gl2.h> #include <GLES2/gl2ext.h>

#include <stdio.h> #include <stdlib.h> #include <math.h>

#include “Shader.vert” #include “Shader.frag”

#include “SenaoCommon.h”

這是開頭需要include沒什麼好講,主要要看的是 #include “Shader.vert” #include “Shader.frag” 這兩個會導入vertrice and fragment shader code,如下:

//Shader.vert文件内容 static const char* VERTEX_SHADER = “attribute vec4 vPosition; \n” “attribute vec2 a_texCoord; \n” “varying vec2 tc; \n” “void main() \n” “{ \n” " gl_Position = vPosition; \n" " tc = a_texCoord; \n" “} \n”;

//Shader.frag文件内容 static const char* FRAG_SHADER = “varying lowp vec2 tc;\n” “uniform sampler2D SamplerY;\n” “uniform sampler2D SamplerU;\n” “uniform sampler2D SamplerV;\n” “void main(void)\n” “{\n” “mediump vec3 yuv;\n” “lowp vec3 rgb;\n” “yuv.x = texture2D(SamplerY, tc).r;\n” “yuv.y = texture2D(SamplerU, tc).r - 0.5;\n” “yuv.z = texture2D(SamplerV, tc).r - 0.5;\n” “rgb = mat3( 1, 1, 1,\n” “0, -0.39465, 2.03211,\n” “1.13983, -0.58060, 0) * yuv;\n” “gl_FragColor = vec4(rgb, 1);\n” “}\n”;

這段shader code之前出現過很多次了,這邊就跳過不介紹了。

int ATTRIB_VERTEX,ATTRIB_TEXTURE;

這邊是ATTRIB_VERTEX跟ATTRIB_TEXTURE。 很重要等等會用到。

static GLuint g_texYId; static GLuint g_texUId; static GLuint g_texVId; static GLuint simpleProgram;

static char * g_buffer = NULL; static int g_width = 0; static int g_height = 0; static int backing_width = 0; static int backing_height = 0;

一些參數,也是之前介紹ios時有看過的, 三個texture,program,g_buffer(pixel data), g_width (frame width), g_height(frame height), backing_width(view width), backing_height(view height).

static GLfloat squareVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, };

這邊頂點座標也是跟ios一樣。

static void checkGlError(const char* op) { GLint error; for (error = glGetError(); error; error = glGetError()) { LOGI(“error::after %s() glError (0x%x)\n”, op, error); } }

印出log,LOGI是定義在senaoCommon裡的。

static GLuint buildShader(const char* source, GLenum shaderType) { GLuint shaderHandle = glCreateShader(shaderType);

if (shaderHandle)
{
    glShaderSource(shaderHandle, 1, &source, 0);
    glCompileShader(shaderHandle);

    GLint compiled = 0;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compiled);
    if (!compiled)
    {
        GLint infoLen = 0;
        glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen)
        {
            char* buf = (char*) malloc(infoLen);
            if (buf)
            {
                glGetShaderInfoLog(shaderHandle, infoLen, NULL, buf);
                LOGE("error::Could not compile shader %d:\n%s\n", shaderType, buf);
                free(buf);
            }
            glDeleteShader(shaderHandle);
            shaderHandle = 0;
        }
    }else{

LOGI(“compiled shader”); }

}

return shaderHandle;

}

buildShader用來建立shader,glShaderSource導入source code,glCompileShader進行編譯。 if (!compiled) 編譯不成功,印出訊息。

static GLuint buildProgram(const char* vertexShaderSource, const char* fragmentShaderSource) { GLuint vertexShader = buildShader(vertexShaderSource, GL_VERTEX_SHADER); GLuint fragmentShader = buildShader(fragmentShaderSource, GL_FRAGMENT_SHADER); GLuint programHandle = glCreateProgram();

if (programHandle)
{
    glAttachShader(programHandle, vertexShader);
    checkGlError("glAttachShader");
    glAttachShader(programHandle, fragmentShader);
    checkGlError("glAttachShader");
    glLinkProgram(programHandle);

    GLint linkStatus = GL_FALSE;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkStatus);
    if (linkStatus != GL_TRUE) {
        GLint bufLength = 0;
        glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &bufLength);
        if (bufLength) {
            char* buf = (char*) malloc(bufLength);
            if (buf) {
                glGetProgramInfoLog(programHandle, bufLength, NULL, buf);

// log_easy(“error::Could not link program:\n%s\n”, buf); free(buf); } } glDeleteProgram(programHandle); programHandle = 0; }

}

return programHandle;

}

GLuint vertexShader = buildShader(vertexShaderSource, GL_VERTEX_SHADER);
GLuint fragmentShader = buildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
GLuint programHandle = glCreateProgram();

glGetProgramiv(programHandle, GL_LINK_STATUS, &linkStatus); 判斷link status,失敗就印出訊息。

void gl_initialize() { g_buffer = NULL;

LOGI("OPEN_GL Init");

simpleProgram = buildProgram(VERTEX_SHADER, FRAG_SHADER);
glUseProgram(simpleProgram);
glGenTextures(1, &g_texYId);
glGenTextures(1, &g_texUId);
glGenTextures(1, &g_texVId);

}

一開使要先call 此init函式。 先build program再use。 產生三個textures(YUV)。

void gl_uninitialize() {

g_width = 0;
g_height = 0;

if (g_buffer)
{
    free(g_buffer);
    g_buffer = NULL;
}

}

結束時call此uninit函式進行釋放。

//設置圖像數據 void gl_set_framebuffer(const char* buffer, int buffersize, int width, int height) {

if (g_width != width || g_height != height)
{
    if (g_buffer)
        free(g_buffer);

    g_width = width;
    g_height = height;

    g_buffer = (char *)malloc(buffersize);
}

if (g_buffer)
    memcpy(g_buffer, buffer, buffersize);

}

傳入frame data,這邊進行memcpy,將data拷貝,之後在繪製texture時會用到。 我覺得應該有辦法省掉這個copy,這樣可以讓效率提升一些。

void updateVertices(int glWidth, int glHeight);

先宣告函式,因為等等要用。

void gl_setbackingAndGLWH(int backingWidth, int backingHeight, int glWidth, int glHeight){ backing_width = backingWidth; backing_height = backingHeight; updateVertices(glWidth, glHeight); }

傳入畫面寬高(就是surfaceview的寬高)與frame寬高。 並且更新頂點座標。

void updateVertices(int glWidth, int glHeight) { const bool fit = true; const float width = glWidth; const float height = glHeight; const float dH = (float)backing_height / height; const float dW = (float)backing_width / width; const float dd = fit ? (dH<dW ? dH:dW) : (dH>dW ? dH:dW); const float h = (height * dd / (float)backing_height); const float w = (width * dd / (float)backing_width );

squareVertices[0] = - w; squareVertices[1] = - h; squareVertices[2] = w; squareVertices[3] = - h; squareVertices[4] = - w; squareVertices[5] = h; squareVertices[6] = w; squareVertices[7] = h; }

更新頂點座標的算法是把ios的code寫成android,所以運作原理同ios。 根據fit來決定顯示方式: fit為true時,畫面不裁切,會變形。 fit為false時,畫面會裁切,不會變形。

//畫屏 void gl_render_frame() { if (0 == g_width || 0 == g_height) return;

const char *buffer = g_buffer;
int width = g_width;
int height = g_height;

glViewport(0, 0, backing_width, backing_height);
bindTexture(g_texYId, buffer, width, height);
bindTexture(g_texUId, buffer + width * height, width/2, height/2);
bindTexture(g_texVId, buffer + width * height * 5 / 4, width/2, height/2);

renderFrame(); } //如果設置圖像數據和花屏是兩個線程的話,記住要加鎖。

呼叫gl_render_frame用以繪圖, g_width,g_height是frame大小, glViewport(0, 0, backing_width, backing_height);是設定視圖大小,範圍就是surfaceview的大小,

然後透過bindTexture設定texture,這邊有個跟ios不一樣的地方, 就是ios分成三個data傳入,分別對應Y,U,V, 而這邊只傳入一個g_buffer,這個g_buffer是內含YUV三組data, 所以透過 buffer, buffer + width * height, buffer + width * height * 5 / 4,就可以讀到所需要的data。 Y size: widthheight U size: width/2height/2 V szie: width/2*height/2

static GLuint bindTexture(GLuint texture, const char *buffer, GLuint w , GLuint h) { glBindTexture ( GL_TEXTURE_2D, texture ); checkGlError(“glBindTexture”); glTexImage2D ( GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, buffer); checkGlError(“glTexImage2D”); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); checkGlError(“glTexParameteri”); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); checkGlError(“glTexParameteri”); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); checkGlError(“glTexParameteri”); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); checkGlError(“glTexParameteri”);

return texture;

}

這邊一樣bind YUV textures, 並設定GL_TEXTURE_MIN_FILTER,GL_TEXTURE_MAG_FILTER ,GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T。

static void renderFrame() { static GLfloat coordVertices[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, };

glClearColor(0.5f, 0.5f, 0.5f, 1);
checkGlError("glClearColor");
glClear(GL_COLOR_BUFFER_BIT);
checkGlError("glClear");
GLint tex_y = glGetUniformLocation(simpleProgram, "SamplerY");
checkGlError("glGetUniformLocation");
GLint tex_u = glGetUniformLocation(simpleProgram, "SamplerU");
checkGlError("glGetUniformLocation");
GLint tex_v = glGetUniformLocation(simpleProgram, "SamplerV");
checkGlError("glGetUniformLocation");

ATTRIB_VERTEX=glGetAttribLocation(simpleProgram,"vPosition");
checkGlError("glBindAttribLocation");

ATTRIB_TEXTURE=glGetAttribLocation(simpleProgram,"a_texCoord");
checkGlError("glBindAttribLocation");

glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
checkGlError("glVertexAttribPointer");
glEnableVertexAttribArray(ATTRIB_VERTEX);
checkGlError("glEnableVertexAttribArray");

glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, coordVertices);
checkGlError("glVertexAttribPointer");
glEnableVertexAttribArray(ATTRIB_TEXTURE);
checkGlError("glEnableVertexAttribArray");

glActiveTexture(GL_TEXTURE0);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, g_texYId);
checkGlError("glBindTexture");
glUniform1i(tex_y, 0);
checkGlError("glUniform1i");

glActiveTexture(GL_TEXTURE1);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, g_texUId);
checkGlError("glBindTexture");
glUniform1i(tex_u, 1);
checkGlError("glUniform1i");

glActiveTexture(GL_TEXTURE2);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, g_texVId);
checkGlError("glBindTexture");
glUniform1i(tex_v, 2);
checkGlError("glUniform1i");

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
checkGlError("glDrawArrays");

}

coordVertices跟ios一樣。 glClearColor(0.5f, 0.5f, 0.5f, 1); 顏色跟ios不同,不過這沒差,只是清除時的顏色而已。 checkGlError(“glClearColor”);

glClear(GL_COLOR_BUFFER_BIT);
checkGlError("glClear");

用上一行設定的清除顏色進行清除。

GLint tex_y = glGetUniformLocation(simpleProgram, "SamplerY");
checkGlError("glGetUniformLocation");
GLint tex_u = glGetUniformLocation(simpleProgram, "SamplerU");
checkGlError("glGetUniformLocation");
GLint tex_v = glGetUniformLocation(simpleProgram, "SamplerV");
checkGlError("glGetUniformLocation");

取得sampler YUV location。

ATTRIB_VERTEX=glGetAttribLocation(simpleProgram,"vPosition");
checkGlError("glBindAttribLocation");

ATTRIB_TEXTURE=glGetAttribLocation(simpleProgram,"a_texCoord");
checkGlError("glBindAttribLocation");

可以取得 attribute的location。

glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
checkGlError("glVertexAttribPointer");
glEnableVertexAttribArray(ATTRIB_VERTEX);
checkGlError("glEnableVertexAttribArray");

glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, coordVertices);
checkGlError("glVertexAttribPointer");
glEnableVertexAttribArray(ATTRIB_TEXTURE);
checkGlError("glEnableVertexAttribArray");

傳入data給attribute。

glActiveTexture(GL_TEXTURE0);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, g_texYId);
checkGlError("glBindTexture");
glUniform1i(tex_y, 0);
checkGlError("glUniform1i");

glActiveTexture(GL_TEXTURE1);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, g_texUId);
checkGlError("glBindTexture");
glUniform1i(tex_u, 1);
checkGlError("glUniform1i");

glActiveTexture(GL_TEXTURE2);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, g_texVId);
checkGlError("glBindTexture");
glUniform1i(tex_v, 2);
checkGlError("glUniform1i");

active三個textures(YUV),並且傳入給uniform sampler2D。

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
checkGlError("glDrawArrays");

繪製,關於glDrawArrays前面ios也有介紹過,是一樣的。

到這邊android ndk opengl render講完了, 下次會提到EGL, 這個EGL是用來連接view與opengl,其概念有點像SDL,在ios中也是用到EGL(ios叫EAGL)。