一、概述
案例:使用opengles+egl渲染一張圖片
關鍵類介紹:
1.新建一個DrawPictureActivity.java用於充當顯示容器:初始化SurfaceView並設置SurfaceView的callback回調函數。並在其onSurfaceCreated函數中對DrawPicture對象進行初始化。
2.新建DrawPicture.java用於java層和native層進行通訊
3.新建picture_controller.cpp用於和DrawPicture.java溝通java傳遞給jni層的數據。並在其init方法中創建ANativeWindow、提取Bitmap中的像素數組,初始化C++類Picture類
4.新建picture.cpp類,此類為整個渲染過程提供:OpenGL ES上下文環境、顯示設備EGLSurface、GPU程序(shader)、OpenGL ES程序
渲染步驟:
1.DrawPictureActivity.java中在其SurfaceHolder.addCallback的回調函數中初始化DrawPicture.java類,並調用DrawPicture對象的initBitmap方法將客戶端的bitmap傳遞給jni層
2.在jni層的(picture_controller.cpp)initBitmap方法中,根據傳遞過來的Surface對ANativeWindow進行創建,根據傳遞過來的Bitmap對其中的像素進行提取。
3.實例化C++的Picture並調用其init方法,在init方法中會初始化OpenGL ES上下文環境以及顯示設備EGLSurface,並調用eglMakeCurrent來為當前線程綁定上下文及顯示設備。
3.調用C++的Picture實例中的renderPicture方法,通過此方法,a.創建一個紋理id、綁定紋理id、設置紋理的過濾方式及平鋪方式、並將從第二步中提取出來的像素數組上傳到這個紋理上。b.創建shader 。c.創建opengles程序 。d.給opengles着色器傳遞坐標數據(頂點坐標、紋理坐標)。e.調用glDrawArray方法開始繪制紋理 f.調用eglSwapBuffers方法將back frame buffer 和front frame buffer交互,將圖像顯示到用戶屏幕上。
二、關鍵代碼
1.DrawPictureActivity.java:初始化SurfaceView並得到SurfaceHolder,然后給SurfaceHolder設置回調函數
surfaceView = findViewById(R.id.pictureSurfaceView); holder = surfaceView.getHolder(); holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { drawPicture = new DrawPicture(); parseBitmap(); Log.e("surfaceCreated", "" + pixels.length); // drawPicture.initByte(bitmapWidth, bitmapHeight, bitmap2RGB(bitmap), holder.getSurface()); drawPicture.initBitmap(bitmap,holder.getSurface()); } @Override public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { drawPicture.onSurfaceChanged(width,height); } @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { } });
2.DrawPicture.java:和jni溝通的類
public native void initBitmap(Bitmap bitmap,Surface surface); /** * 當surface發生改變的時候更新width、height * * @param mWidth 寬度 * @param mHeight 高度 */ public native void onSurfaceChanged(int mWidth, int mHeight);
3.picture_controller.cpp :提取bitmap像素數組、初始化ANativeWindow、初始化Picture C++類
extern "C" JNIEXPORT void JNICALL Java_com_yw_ywmediaplayer_activity_nativeinterface_DrawPicture_initBitmap(JNIEnv *env, jobject thiz, jobject bitmap, jobject surface) { void* pixels =NULL; AndroidBitmapInfo bitmapInfo; int ret = AndroidBitmap_getInfo(env,bitmap,&bitmapInfo); if(ret<0){ LOGE("get bitmap info error"); return; } LOGE("bitmapInfo:width=[%d],height=[%d]",bitmapInfo.width,bitmapInfo.height); if(bitmapInfo.format!=ANDROID_BITMAP_FORMAT_RGBA_8888){ LOGE("not support RGBA8888"); return; } ret = AndroidBitmap_lockPixels(env,bitmap,&pixels); if(ret<0){ LOGE("get bitmap pixels fail"); return; } _window = ANativeWindow_fromSurface(env, surface); LOGE("實例化Picture"); picture = new Picture(bitmapInfo.width, bitmapInfo.height, pixels, _window); LOGE("picture->init()"); picture->init(); AndroidBitmap_unlockPixels(env,bitmap); }
3.Picture C++類
a.初始化OpenGL ES上下文環境及顯示設備
/** * 初始化egl並創建eglsurface */ void Picture::init() { eglCore = new EGLCore(); eglCore->init(); eglSurface = eglCore->createWindowSurface(_window); //給當前線程綁定上下文環境及顯示設備 eglCore->makeCurrent(eglSurface); }
b.設置窗口大小及清除各種緩沖區
void Picture::initViewPort() { //設置窗口大小 glViewport(0, 0, screenWidth, screenHeight); LOGE("screenWidth=[%d],screenHeight=[%d]",screenWidth,screenHeight); glClearColor(0.0f, 0.0f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //reset xoffset yoffset resizeTexture(); }
c.創建紋理id、綁定紋理id、設置紋理過濾方式及紋理的重復映射和簡約映射方式、將像素數組上傳到紋理上
/**創建紋理*/ //創建並綁定紋理 LOGE("glGenTextures"); GLuint textureId = 0; glGenTextures(1, &textureId); LOGE("glBindTexture"); //此處創建紋理以后需要綁定紋理id,並給紋理設置過濾方式及映射方式,不然會黑屏 glBindTexture(GL_TEXTURE_2D, textureId);//在opengles操作過程中必須告訴opengles操作的是哪個紋理,所以要調用操作opengles提供的綁定紋理的方法綁定紋理 /**設置紋理參數*/ //設置紋理的過濾方式(雙線性過濾):當紋理對象(可以理解為一張圖片)被渲染到物體表面上的時候(實際上是OpenGL繪制管線將紋理的元素映射到OpenGL生成的片段上的時候),有可能要被放大或者縮小,而當其放大或者縮小的時候,具體應該如何確定每個像素是如何被填充的,就由開發者配置的紋理對象的紋理過濾器來指明 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //設置紋理映射過程中用到的重復映射和簡約映射規則:將該紋理的s軸和t軸的坐標設置為GL_CLAMP_TO_EDGE類型,因為紋理坐標可以超出(0,1)的范圍,而按照上述設置規則,所有大於1的紋理值都要設置為1,所有小於0的值都要置為0。 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); /**將RGBA表示的橡樹數組上傳到紋理上*/ //將RGBA數組表示的像素內容上傳到紋理上 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmapWidth, bitmapHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t *) pixels);
d.創建opengles可執行程序及着色器
/**創建opengles顯卡可執行程序*/ //創建opengles程序 LOGE("glCreateProgram"); GLuint programId = glCreateProgram(); GLuint vertexShader = createShader(GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE); GLuint framgentShader = createShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE); LOGE("glAttachShader"); glAttachShader(programId, vertexShader); glAttachShader(programId, framgentShader); LOGE("glLinkProgram"); glLinkProgram(programId); GLint status; glGetProgramiv(programId, GL_LINK_STATUS, &status); if (status == GL_FALSE) { GLint bufLength = 0; glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength); if (bufLength) { char *buf = (char *) malloc(bufLength); if (buf) { glGetProgramInfoLog(programId, bufLength, NULL, buf); LOGI("Could not link program:\n%s\n", buf); free(buf); } } glDeleteProgram(programId); programId = 0; return -1; } GLint mPositionHandle = glGetAttribLocation(programId, "position"); GLint mTextureHandle = glGetAttribLocation(programId, "texcoord"); GLint mUniformSampler = glGetUniformLocation(programId, "yuvTexSampler"); LOGE("glUseProgram"); glUseProgram(programId);
/** * 創建shader * @param shaderType shader類型 * @param source shader程序字符串 * @return shader */ GLuint Picture::createShader(GLenum shaderType, const char *source) { GLint status; GLuint shader = glCreateShader(shaderType); glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen) { char *buf = (char *) malloc(infoLen); if (buf) { glGetShaderInfoLog(shader, infoLen, NULL, buf); LOGI("Could not compile shader %d:\n%s\n", shaderType, buf); free(buf); } } else { LOGI("Guessing at GL_INFO_LOG_LENGTH size\n"); char *buf = (char *) malloc(0x1000); if (buf) { glGetShaderInfoLog(shader, 0x1000, NULL, buf); LOGI("Could not compile shader %d:\n%s\n", shaderType, buf); free(buf); } } glDeleteShader(shader); shader = 0; } return shader; }
e.設置頂點坐標、紋理坐標、激活紋理並使用紋理
LOGE("glGetAttribLocation"); //設置頂點坐標 glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, 0, 0, VERTEXS_POINTS); glEnableVertexAttribArray(mPositionHandle); //設置紋理坐標 LOGE("glGetAttribLocation"); GLfloat TEXTURE_POINTS[] = { xOffset, yOffset, xOffset, 1.0f - yOffset, 1.0f - xOffset, yOffset, 1.0f - xOffset, 1.0f - yOffset }; glVertexAttribPointer(mTextureHandle, 2, GL_FLOAT, 0, 0, TEXTURE_POINTS); glEnableVertexAttribArray(mTextureHandle); glActiveTexture(GL_TEXTURE0); // glBindTexture(GL_TEXTURE_2D, textureId); glUniform1i(mUniformSampler, 0); checkGlError("glUniform1i");
f.開始繪制
//開始繪制 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
g.將back frame buffer和front frame buffer進行交換,並將圖像顯示到屏幕上
LOGE("將front frame buffer 與back frame buffer交換並顯示到屏幕上"); eglCore->swapBuffers(eglSurface);
h.到此結束,運行后就能顯示出圖片了。
三、示例圖片