一、初始化PBO环境
如果你的minSdkVersion小于18,那么你需要下载google提供的gl3stub.c文件,不然编译都通过不了,会一直报错找不到GLESv3,具体可以参考google提供的demo:
https://github.com/googlesamples/android-ndk/tree/master/gles3jni
下载好gl3stub.h和gl3stub.c后把它放到你的工程jni目录下,编译选项加入它们,然后在你的cpp或者c文件内引用它,比如:
#include <GLES2/gl2.h>
#include "gl3stub.h"
注意:
1、 这个头文件里面只包含了GLES3.0独有的api,所以你如果还用到了GLES2.0的api,你需要包含gl2.h,这样调用才不会报错
2、 在包含gl3stub头文件后还不能使用3.0特性,如果你认真看gl3stub文件内容你会发现需要手动调用gl3stubInit();这个函数后才能真正开始调用
二、创建PBO
因为PBO是3.0才有的特性,所以在上面初始化环境后就可以使用PBO了,首先创建两个pbo用于交替使用,至于为什么创建两个后面会逐步介绍:
void initPBO() {
glVersion = (const char *) glGetString(GL_VERSION);
logd("initPBO opengl version:%s", glVersion);
//android版本需要低于18,使用gl3stub.c
gl3stubInit();
mPboIndex = 0;
mPboNewIndex = 1;
mPboSize = mWidth * mHeight * 4;
glGenBuffers(2, mPboIds);
if (mPboIds[0] == 0 || mPboIds[1] == 0) {
logd("%s", "generate pbo fail");
return;
}
logd("PBO ID :%d,%d", mPboIds[0], mPboIds[1]);
//绑定到第一个PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPboIds[0]);
//设置内存大小
glBufferData(GL_PIXEL_PACK_BUFFER, mPboSize, NULL, GL_STATIC_READ);
//绑定到第而个PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPboIds[1]);
//设置内存大小
glBufferData(GL_PIXEL_PACK_BUFFER, mPboSize, NULL, GL_STATIC_READ);
//解除绑定PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
bool setupEGL(int width, int height, int textureSize) {
EGLConfig config;
EGLint numConfigs;
bool result = true;
const EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_NONE
};
const EGLint pbuf_attribs[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE};
const EGLint mEGLContextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
if ((mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
logd("Tex eglGetDisplay() returned error %d", eglGetError());
result = false;
} else {
bool ret = eglInitialize(mEGLDisplay, 0, 0);
if (!ret) {
logd("init mEGLDisplay fail");
result = false;
}
}
if (!eglChooseConfig(mEGLDisplay, attribs, &config, 1, &numConfigs)) {
logd("eglChooseConfig() returned error %d", eglGetError());
result = false;
}
if (!(mEGLContext = eglCreateContext(mEGLDisplay, config, 0, mEGLContextAttribs))) {
logd("Tex eglCreateContext() returned error %d", eglGetError());
}
if (!(mEGLSurface = eglCreatePbufferSurface(mEGLDisplay, config, pbuf_attribs))) {
logd("Tex eglCreatePbufferSurface() returned error %d", eglGetError());
result = false;
}
logd("About to make current. Display %p surface %p mEGLContext %p", mEGLDisplay, mEGLSurface,
mEGLContext);
if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
logd("Tex eglMakeCurrent() returned error %d", eglGetError());
result = false;
}
if (result) {
mWidth = width;
mHeight = height;
mTextureSize = textureSize;
initTexture();
}
return result;
}
uint8_t *readDataFromGPU(int width, int height) {
glVersion = (const char *) glGetString(GL_VERSION);
if (strcmp(glVersion, "OpenGL ES 3.0") >= 0) {
// 保存完后初始化mInitRecord
if (!mInitRecord) {
mWidth = width;
mHeight = height;
initPBO();
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPboIds[mPboIndex]);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
if (!mInitRecord) {//第一帧没有数据跳出
unbindPixelBuffer();
mInitRecord = true;
return NULL;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPboIds[mPboNewIndex]);
//glMapBufferRange会等待DMA传输完成,所以需要交替使用pbo,这边获取的是上一帧的内容
uint8_t *byteBuffer = (uint8_t *) glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0,
mPboSize,
GL_MAP_READ_BIT);
if (byteBuffer == NULL) {
loge("%s", "map buffer fail");
}
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
unbindPixelBuffer();
return byteBuffer;
} else {
int size = width * height * 4;
uint8_t *data = (uint8_t *) malloc(sizeof(uint8_t) * size);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
return data;
}
}
这边就有几点需要说明一下了,
1、 首先是为什么使用2个PBO,
a) 可以看到第一次调用这个函数只调用了glReadPixels,然后就返回了,这样做的目的是通知GPU把数据拷贝到第一个pbo里面,而这个拷贝过程是异步的,所以glReadPixels会立即返回,
b) 第二次调用这个函数时之前要GPU拷贝的数据拷贝完了,这时候再让GPU继续拷贝数据到第二个pbo中,然后去处理第一个pbo中已经拷贝完的数据,这样的话每次就不用等待GPU慢慢拷贝数据,于是时间上面就缩短了将近一半。
c) 然后为什么是2个不是3个或4个呢,因为你每次处理一个pbo的时间小于等待GPU拷贝一个pbo时间的时候就不会出现处理来不及的情况,这样也就不需要其他pbo存放GPU数据了。
2、 如果是YUV数据的话,glReadPixels大小就得改了,如下:
a) glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
b) 因为你的渲染脚本已经将yuv转成RGBA了,所以读出来的大小就要按RGBA的大小,而不是yuv原先的大小
3、 glMapBufferRange的作用就是将pbo的地址映射到CPU来,所以这个地址是可以直接使用的,不用再调用memcpy来拷贝一遍,不然反而失去了PBO的优势
4、 glReadPixels传入的数组必须是malloc或者new创建的,不能是直接声明大小的,比如uint8_t data[size]这种是会出现fault addr错误,new 或者malloc才不会报错
5、 这边再强调一下,pbo是针对连续多帧渲染保存这种情况比较有优势,所以一般用于视频的渲染保存,单个pbo或者只读取一两次就不需要使用pbo了,不会提升多大速度