Jni调用PBO提高读取速度

一、初始化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了,不会提升多大速度
 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值