关于opengl-es:Opengl-ES之PBO

叨叨一句几句

对于Opengl的系列曾经有较长的一段时间没有更新了,然而这个系列还远没有到结束境地,后续至多还有对于Opengl矩阵变换、YUV与RGB互转、Opengl水印贴图、Opengl转场动画等主题文章。

断更的次要起因如果给本人找个借口的话能够说是工作比价忙,如果说的比拟事实一点就是本人懒且没啥能源,毕竟写技术博客文章是一件工夫老本投入很大,而收益产出极小的一件事件…

进入正题…

理解过Opengl的童鞋们都晓得,在Opengl中存在这个各种O,例如VAO、VBO、FBO等,而呈现各种各样的O个别都是因为思考到性能的起因。

明天咱们要介绍的配角PBO,它和之前咱们介绍VBO很像,理解完PBO之后童鞋们能够比照一下PBO与VBO的差别点。

上面从两个方面介绍PBO,什么是PBO以及如何应用PBO。

本文首发于微信公总号号:思想觉悟

更多对于音视频、FFmpeg、Opengl、C++的原创文章请关注微信公众号:思想觉悟

什么是PBO

PBO(Pixel Buffer Object像素缓冲对象)。在理解什么是PBO之前,咱们先来理解一下为什么会呈现PBO这么一个货色?

所谓存在即正当,用倒退的眼光看问题,PBO的呈现必定是为了代替某种货色,或者是为了解决某个问题。

在应用Opengl的时候常常须要在GPU和CPU之间传递数据,例如在应用Opengl将YUV数据转换成RGB数据时就须要先将YUV数据上传到GPU,个别应用函数glTexImage2D,处理完毕后再将RGB后果数据读取到CPU,
这时应用函数glReadPixels即可将数据取回。然而这两个函数都是比拟迟缓的,特地是在数据量比拟大的时候。PBO就是为了解决这个拜访慢的问题而产生的。

PBO能够让咱们通过一个内存指针,间接拜访显存(GPU)的数据,咱们将这块内存指针称作缓冲区。咱们能够通过函数glMapBuffer失去它的内存指针,而后就对这块缓冲区的数据能够随心所欲了。

例如应用函数glReadPixels本来是要传一个内存指针进去的,然而有了缓冲区,它就能够把数据间接复制到缓冲区中去,而不是复制到内存条中去,这样就大大提高了数据的传递效率。

PBO的次要长处是通过间接内存拜访的形式与显存进行疾速像素数据传输,而无需占用CPU周期。

可能看到这张图你没什么感觉,然而比照看看上面这张CPU与GPU间接传递数据的图你就会有所发现了:

留神:PBO是OpenGL ES 3.0开始提供的一种形式,次要利用于从内存疾速复制纹理到显存,或从显存复制像素数据到内存。

PBO的应用形式

既然PBO这么有效率,那么咱们在什么状况下可能会用到PBO呢?有个常见的例子,例如咱们在安卓上开发Camera利用录制视频时,如果须要用到x264进行软编码的话可能就会用到PBO,
首先咱们将相机纹理图像送到Surface渲染显示,而后将Surface数据应用PBO的形式读取解决送到X264编码器中进行编码,当然在安卓上你也能够应用ImageReader…

上面咱们来介绍下PBO的应用形式。

PBO的创立和初始化相似于VBO,然而在应用的时候须要用到GL_PIXEL_UNPACK_BUFFER GL_PIXEL_PACK_BUFFER 这两个令牌,其中GL_PIXEL_UNPACK_BUFFER绑定示意该PBO用于将像素数据从程序(CPU)传送到OpenGL中;绑定为GL_PIXEL_PACK_BUFFER示意该PBO用于从OpenGL中读回像素数据。

  1. Pbo创立

先上代码,跟着正文看

void PBOOpengl::initPbo() {

    int imgByteSize = imageWidth * imageHeight * 4; // RGBA

    glGenBuffers(1, &uploadPboId);
    // 绑定pbo
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPboId);
    // 设置pbo内存大小
    // 这一步非常重要,第2个参数指定了这个缓冲区的大小,单位是字节,肯定要留神
    //  而后第3个参数是初始化用的数据,如果你传个内存指针进去,这个函数就会把你的
    //  数据复制到缓冲区里,咱们这里一开始并不需要什么数据,所以传个nullptr就行了
    glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, nullptr, GL_STREAM_DRAW);
    // 解除绑定
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    glGenBuffers(1, &downloadPboId);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
    glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, nullptr, GL_STREAM_DRAW);
    // 解除绑定
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    LOGD("uploadPboId:%d---downloadPboId:%d---imgByteSize:%d", uploadPboId, downloadPboId,
         imgByteSize);
}

下面的代码创立了两个PBO,其中uploadPboId用于纹理上传,downloadPboId用于纹理下载。创立好PBO之后而后应用两个PBO专用的令牌进行绑定,之后就调用glBufferData给PBO调配缓冲区,当然,你也能够在应用的时候先进行绑定,而后从新调用glBufferData调配新的缓冲区。

  1. Pbo上传纹理

所谓上传纹理是值将纹理数据从CPU传递到OpenGL,应用Pbo上传纹理时须要先应用令牌GL_PIXEL_UNPACK_BUFFER绑定对应的PBO,而后才行应用PBO的缓冲区:

// 单个PBO测试
void PBOOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel");
    imageWidth = width;
    imageHeight = height;
    // Pbo初始化
    initPbo();

    glGenTextures(1, &imageTextureId);

    // 激活纹理,留神以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
    // 默认是0,如果不是0的话,须要在onDraw的时候从新激活一下?
//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一样的
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);
    
    // 本文首发于微信公总号号:思想觉悟
    // 更多对于音视频、FFmpeg、Opengl、C++的原创文章请关注微信公众号:思想觉悟

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, imageTextureId);
    // 为以后绑定的纹理对象设置盘绕、过滤形式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // pixels参数传递空,前面会通过pbo更新纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // 生成mip贴图
    glGenerateMipmap(GL_TEXTURE_2D);

    int dataSize = width * height * 4;
    // 应用Pbo
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPboId);
    // 将纹理数据拷贝进入缓冲区
    GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
                                                   dataSize,
                                                   GL_MAP_WRITE_BIT);
    if (bufPtr) {
        memcpy(bufPtr, data, static_cast<size_t>(dataSize));
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }
    // 将pbo缓冲区中的数据拷贝到纹理,调用 glTexSubImage2D 后立刻返回,不影响 CPU 时钟周期
    // 这个函数会判断 GL_PIXEL_UNPACK_BUFFER 这个中央有没有绑定一个缓冲区
    //   如果有,就从这个缓冲区读取数据,而不是data参数指定的那个内存
    // 这样glTexSubImage2D就会从咱们的缓冲区中读取数据了
    // 这里为什么要用glTexSubImage2D呢,因为如果用glTexImage2D,glTexImage2D会销毁纹理内存从新申请,glTexSubImage2D就仅仅只是更新纹理中的数据,这就进步了速度,并且优化了显存的利用率
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    // Pbo解除
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}

正文曾经很具体了,就不多解析了,还是看不懂的私聊交换呗…

  1. Pbo下载纹理

所谓上传纹理是值将纹理数据从OpenGL中读取回CPU,与上传纹理一样,下载纹理也是须要先应用令牌绑定PBO能力应用,下载纹理应用的令牌是GL_PIXEL_PACK_BUFFER
上面的代码作用是将Opengl的渲染后果应用PBO读取进去:

// 单PBO读取测试
void PBOOpengl::readPixel(uint8_t **data,int *width,int *height) {

    *width = eglHelper->viewWidth;
    *height = eglHelper->viewHeight;
    int dataSize = eglHelper->viewWidth * eglHelper->viewHeight * 4;
    *data = new uint8_t[dataSize];
    // 办法一 失常的glReadPixels读取 最简略
//    glReadPixels(0, 0, eglHelper->viewWidth, eglHelper->viewHeight,
//                 GL_RGBA, GL_UNSIGNED_BYTE, *data);

    // 办法二 pbo读取
    // 首先咱们要把缓冲区绑定到 GL_PIXEL_PACK_BUFFER 这个中央
    glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
    // 重新分配一下空间 如果必要,这里就懒得判断了
    glBufferData(GL_PIXEL_PACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
    // 这个函数会判断 GL_PIXEL_PACK_BUFFER 这个中央有没有绑定一个缓冲区,如果有,那就把数据写入到这个缓冲区里
    // 前4个参数就是要读取的屏幕区域,不多解释
    //  格局是RGB,类型是BYTE,每个像素1字节
    // 如果GL_PIXEL_PACK_BUFFER有绑定缓冲区,最初一个参数就作为偏移值来应用,传nullptr就行
    glReadPixels(0, 0, *width, *height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    // 好了咱们曾经胜利把屏幕的像素数据复制到了缓冲区里

    // 这时候,你能够用 glMapBuffer 失去缓冲区的内存指针,来读取外面的像素数据,保留到图片文件
    // 留神glMapBuffer的第1个参数不肯定要是GL_PIXEL_PACK_BUFFER,你能够把缓冲区绑定到比方下面init函数的GL_ARRAY_BUFFER
    //  而后这里也传GL_ARRAY_BUFFER,因为懒得再绑定一次,就接着用下面绑定的GL_PIXEL_PACK_BUFFER吧
    GLubyte *mapPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dataSize,
                                                              GL_MAP_READ_BIT));
    if (nullptr != mapPtr)
    {
        LOGD("readPixel 数据拷贝");
        // 拷贝数据
        memcpy(*data,mapPtr,dataSize);
        // 解除Map
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    } else{
        LOGD("readPixel glMapBufferRange null");
    }
    // 完事了把GL_PIXEL_PACK_BUFFER这个中央的缓冲区解绑掉,免得别的函数误操作
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}

其实无论有没有应用PBO都是应用的函数glReadPixels进行读取,然而他们的参数却是不一样的,你发现细节了吗?如果是在PBO模式下应用glReadPixels,则会主动将后果发送到PBO的缓冲区,
并不会像独自应用glReadPixels那样须要阻塞进行期待返回,因而效率就高了…

双PBO

PBO的另一个劣势是它还具备异步DMA(Direct Memory Access)传输,也正因为这个个性,使得在应用单个PBO的状况下,在一些机型上可能性能晋升并不显著,所以通常须要两个PBO配合应用。

通过下面这两张图咱们能够看到利用交替应用双PBO的形式能够将PBO的疾速个性更进一步。

上面的代码展现了如何通过双PBO上传纹理和下载渲染后果:

应用双PBO上传纹理数据

// 双PBO测试
void PBOOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel  uploadPboIndex:%d",uploadPboIndex);
    imageWidth = width;
    imageHeight = height;
    // Pbo初始化
    initPboArray();

    if(imageTextureId == 0){

        glGenTextures(1, &imageTextureId);

        // 激活纹理,留神以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
        // 默认是0,如果不是0的话,须要在onDraw的时候从新激活一下?
//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一样的
        glActiveTexture(GL_TEXTURE2);
        glUniform1i(textureSampler, 2);

        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, imageTextureId);
        // 为以后绑定的纹理对象设置盘绕、过滤形式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // pixels参数传递空,前面会通过pbo更新纹理数据
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

        // 生成mip贴图
        glGenerateMipmap(GL_TEXTURE_2D);
    }

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, imageTextureId);

    int dataSize = width * height * 4;
    // 应用Pbo
    // 指针运算你要会一点呀
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, *(uploadPboIds + uploadPboIndex % NUM_PBO ));
    // 将纹理数据拷贝进入缓冲区
    GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
                                                   dataSize,
                                                   GL_MAP_WRITE_BIT);
    if (bufPtr) {
        memcpy(bufPtr, data, static_cast<size_t>(dataSize));
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }
    // 将pbo缓冲区中的数据拷贝到纹理,调用 glTexSubImage2D 后立刻返回,不影响 CPU 时钟周期
    // 这个函数会判断 GL_PIXEL_UNPACK_BUFFER 这个中央有没有绑定一个缓冲区
    //   如果有,就从这个缓冲区读取数据,而不是data参数指定的那个内存
    // 这样glTexSubImage2D就会从咱们的缓冲区中读取数据了
    // 这里为什么要用glTexSubImage2D呢,因为如果用glTexImage2D,glTexImage2D会销毁纹理内存从新申请,glTexSubImage2D就仅仅只是更新纹理中的数据,这就进步了速度,并且优化了显存的利用率
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    // Pbo解除
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
    // 索引自加
    uploadPboIndex++;
}

应用双PBO下载渲染后果:


// 双PBO读取测试
void PBOOpengl::readPixel(uint8_t **data,int *width,int *height) {

    *width = eglHelper->viewWidth;
    *height = eglHelper->viewHeight;
    int dataSize = eglHelper->viewWidth * eglHelper->viewHeight * 4;
    *data = new uint8_t[dataSize];

    // 首先咱们要把缓冲区绑定到 GL_PIXEL_PACK_BUFFER 这个中央
    // 指针运算你要会一点呀
    glBindBuffer(GL_PIXEL_PACK_BUFFER, *(downloadPboIds + downloadPboIndex % NUM_PBO));
    // 重新分配一下空间
    glBufferData(GL_PIXEL_PACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
    // 这个函数会判断 GL_PIXEL_PACK_BUFFER 这个中央有没有绑定一个缓冲区,如果有,那就把数据写入到这个缓冲区里
    // 前4个参数就是要读取的屏幕区域,不多解释
    //  格局是RGB,类型是BYTE,每个像素1字节
    // 如果GL_PIXEL_PACK_BUFFER有绑定缓冲区,最初一个参数就作为偏移值来应用,传nullptr就行
    glReadPixels(0, 0, *width, *height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    // 好了咱们曾经胜利把屏幕的像素数据复制到了缓冲区里

    // 这时候,你能够用 glMapBuffer 失去缓冲区的内存指针,来读取外面的像素数据,保留到图片文件
    // 留神glMapBuffer的第1个参数不肯定要是GL_PIXEL_PACK_BUFFER,你能够把缓冲区绑定到比方下面init函数的GL_ARRAY_BUFFER
    //  而后这里也传GL_ARRAY_BUFFER,因为懒得再绑定一次,就接着用下面绑定的GL_PIXEL_PACK_BUFFER吧
    GLubyte *mapPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dataSize,
                                                              GL_MAP_READ_BIT));
    if (nullptr != mapPtr)
    {
        LOGD("readPixel 数据拷贝");
        // 拷贝数据
        memcpy(*data,mapPtr,dataSize);
        // 解除Map
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    } else{
        LOGD("readPixel glMapBufferRange null");
    }
    // 完事了把GL_PIXEL_PACK_BUFFER这个中央的缓冲区解绑掉,免得别的函数误操作
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    // Pbo索引自加
    downloadPboIndex++;
}

上面咱们应用双PBO上传纹理和双PBO下载实现一个纹理动静切换和下载渲染后果的小demo,残缺代码如下:

PBOOpengl.h

#ifndef NDK_OPENGLES_LEARN_PBOOPENGL_H
#define NDK_OPENGLES_LEARN_PBOOPENGL_H

#include "BaseOpengl.h"

static const int NUM_PBO = 2;

class PBOOpengl: public BaseOpengl{

public:
    PBOOpengl();
    virtual ~PBOOpengl();
    // override要么就都写,要么就都不写,不要一个虚函数写override,而另外一个虚函数不写override,不然可能编译不过
    virtual void onDraw() override;
    virtual void setPixel(void *data, int width, int height, int length) override;
    virtual void readPixel(uint8_t **data,int *width,int *height) override;
private:
    void initPbo();
    void initPboArray();
    GLint positionHandle{-1};
    GLint textureHandle{-1};
    GLuint vbo{0};
    GLuint vao{0};
    GLuint ebo{0};
    // 自身图像纹理id
    GLuint imageTextureId{0};
    GLint textureSampler{-1};
    int imageWidth{0};
    int imageHeight{0};
    // 上传纹理的pbo
    GLuint uploadPboId{0};
    // cpu下载纹理的pbo
    GLuint downloadPboId{0};

    // 上传纹理的pbo
    GLuint *uploadPboIds{nullptr};
    // cpu下载纹理的pbo
    GLuint *downloadPboIds{nullptr};

    // Pbo 的索引,用于双PBO时
    int uploadPboIndex{0};
    int downloadPboIndex{0};

  // 本文首发于微信公总号号:思想觉悟
    // 更多对于音视频、FFmpeg、Opengl、C++的原创文章请关注微信公众号:思想觉悟
};


#endif //NDK_OPENGLES_LEARN_PBOOPENGL_H

PBOOpengl.cpp:

#include "PBOOpengl.h"

#include "../utils/Log.h"

// 顶点着色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 TexCoord;\n"
                         "void main() {\n"
                         "  TexCoord = aTexCoord;\n"
                         "  gl_Position = aPosition;\n"
                         "}";

// 片元着色器
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "out vec4 FragColor;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D ourTexture;\n"
                              "void main()\n"
                              "{\n"
                              "    FragColor = texture(ourTexture, TexCoord);\n"
                              "}";

const static GLfloat VERTICES_AND_TEXTURE[] = {
        0.5f, -0.5f, // 右下
        // 纹理坐标
        1.0f, 1.0f,
        0.5f, 0.5f, // 右上
        // 纹理坐标
        1.0f, 0.0f,
        -0.5f, -0.5f, // 左下
        // 纹理坐标
        0.0f, 1.0f,
        -0.5f, 0.5f, // 左上
        // 纹理坐标
        0.0f, 0.0f
};

// 真正的纹理坐标在图片的左下角
const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {
        1.0f, -1.0f, // 右下
        // 纹理坐标
        1.0f, 0.0f,
        1.0f, 1.0f, // 右上
        // 纹理坐标
        1.0f, 1.0f,
        -1.0f, -1.0f, // 左下
        // 纹理坐标
        0.0f, 0.0f,
        -1.0f, 1.0f, // 左上
        // 纹理坐标
        0.0f, 1.0f
};

// 应用byte类型比应用short或者int类型节约内存
const static uint8_t indices[] = {
        // 留神索引从0开始!
        // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
        // 这样能够由下标代表顶点组合成矩形
        0, 1, 2, // 第一个三角形
        1, 2, 3  // 第二个三角形
};

PBOOpengl::PBOOpengl():uploadPboIds(nullptr),downloadPboIds(nullptr) {
    initGlProgram(ver, fragment);
    positionHandle = glGetAttribLocation(program, "aPosition");
    textureHandle = glGetAttribLocation(program, "aTexCoord");
    textureSampler = glGetUniformLocation(program, "ourTexture");
    LOGD("program:%d", program);
    LOGD("positionHandle:%d", positionHandle);
    LOGD("textureHandle:%d", textureHandle);
    LOGD("textureSample:%d", textureSampler);
    // VAO
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // vbo
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE,
                 GL_STATIC_DRAW);

    // stride 步长 每个顶点坐标之间相隔4个数据点,数据类型是float
    glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);
    // 启用顶点数据
    glEnableVertexAttribArray(positionHandle);
    // stride 步长 每个色彩坐标之间相隔4个数据点,数据类型是float,色彩坐标索引从2开始
    glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                          (void *) (2 * sizeof(float)));
    // 启用纹理坐标数组
    glEnableVertexAttribArray(textureHandle);

    // EBO
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 这个程序不能乱啊,先解除vao,再解除其余的,不然在绘制的时候可能会不起作用,须要从新glBindBuffer才失效
    // vao解除
    glBindVertexArray(0);
    // 解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // 解除绑定
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    LOGD("program:%d", program);
    LOGD("positionHandle:%d", positionHandle);
    LOGD("colorHandle:%d", textureHandle);
}

void PBOOpengl::initPbo() {

    int imgByteSize = imageWidth * imageHeight * 4; // RGBA

    glGenBuffers(1, &uploadPboId);
    // 绑定pbo
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPboId);
    // 设置pbo内存大小
    // 这一步非常重要,第2个参数指定了这个缓冲区的大小,单位是字节,肯定要留神
    //  而后第3个参数是初始化用的数据,如果你传个内存指针进去,这个函数就会把你的
    //  数据复制到缓冲区里,咱们这里一开始并不需要什么数据,所以传个nullptr就行了
    glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, nullptr, GL_STREAM_DRAW);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
  // 本文首发于微信公总号号:思想觉悟
    // 更多对于音视频、FFmpeg、Opengl、C++的原创文章请关注微信公众号:思想觉悟
    glGenBuffers(1, &downloadPboId);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
    glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, nullptr, GL_STREAM_DRAW);
    // 解除绑定
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    LOGD("uploadPboId:%d---downloadPboId:%d---imgByteSize:%d", uploadPboId, downloadPboId,
         imgByteSize);
}

void PBOOpengl::initPboArray() {
    int imgByteSize = imageWidth * imageHeight * 4; // RGBA
    if(nullptr == uploadPboIds){
        uploadPboIds = new GLuint[NUM_PBO];
        glGenBuffers(NUM_PBO, uploadPboIds);
        for (int i = 0; i < NUM_PBO; ++i) {
            // 绑定pbo
            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, *(uploadPboIds + i));
            // 设置pbo内存大小
            // 这一步非常重要,第2个参数指定了这个缓冲区的大小,单位是字节,肯定要留神
            //  而后第3个参数是初始化用的数据,如果你传个内存指针进去,这个函数就会把你的
            //  数据复制到缓冲区里,咱们这里一开始并不需要什么数据,所以传个nullptr就行了
            glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, nullptr, GL_STREAM_DRAW);
            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
        }
    }

    if(nullptr == downloadPboIds){
        downloadPboIds = new GLuint[NUM_PBO];
        glGenBuffers(NUM_PBO, downloadPboIds);
        for (int i = 0; i < NUM_PBO; ++i) {
            // 绑定pbo
            glBindBuffer(GL_PIXEL_PACK_BUFFER, *(downloadPboIds + i));
            glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, nullptr, GL_STREAM_DRAW);
            glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
        }
    }

}

PBOOpengl::~PBOOpengl() {
    glDeleteBuffers(1, &ebo);
    glDeleteBuffers(1, &vbo);
    glDeleteVertexArrays(1, &vao);
    if(nullptr != uploadPboIds){
        glDeleteBuffers(NUM_PBO,uploadPboIds);
        delete [] uploadPboIds;
        uploadPboIds = nullptr;
    }
  // 本文首发于微信公总号号:思想觉悟
    // 更多对于音视频、FFmpeg、Opengl、C++的原创文章请关注微信公众号:思想觉悟
    if(nullptr != downloadPboIds){
        glDeleteBuffers(NUM_PBO,downloadPboIds);
        delete [] downloadPboIds;
        downloadPboIds = nullptr;
    }
}

void PBOOpengl::onDraw() {

    // 绘制屏幕宽高
    glViewport(0, 0, eglHelper->viewWidth, eglHelper->viewHeight);

    // 绘制到屏幕
    // 清屏
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    // 激活纹理
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, imageTextureId);

    // VBO与VAO配合绘制
    // 应用vao
    glBindVertexArray(vao);
    // 应用EBO
// 应用byte类型节俭内存
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (void *) 0);
    glUseProgram(0);
    // vao解除绑定
    glBindVertexArray(0);

    // 禁用顶点
    glDisableVertexAttribArray(positionHandle);
    if (nullptr != eglHelper) {
        eglHelper->swapBuffers();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}
//
//// 单个PBO测试
//void PBOOpengl::setPixel(void *data, int width, int height, int length) {
//    LOGD("texture setPixel");
//    imageWidth = width;
//    imageHeight = height;
//    // Pbo初始化
//    initPbo();
//
//    glGenTextures(1, &imageTextureId);
//
//    // 激活纹理,留神以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
//    // 默认是0,如果不是0的话,须要在onDraw的时候从新激活一下?
////    glActiveTexture(GL_TEXTURE0);
////    glUniform1i(textureSampler, 0);
//
//// 例如,一样的
//    glActiveTexture(GL_TEXTURE2);
//    glUniform1i(textureSampler, 2);
//
//    // 绑定纹理
//    glBindTexture(GL_TEXTURE_2D, imageTextureId);
//    // 为以后绑定的纹理对象设置盘绕、过滤形式
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//    // pixels参数传递空,前面会通过pbo更新纹理数据
//    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
//
//    // 生成mip贴图
//    glGenerateMipmap(GL_TEXTURE_2D);
//
//    int dataSize = width * height * 4;
//    // 应用Pbo
//    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPboId);
//    // 将纹理数据拷贝进入缓冲区
//    GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
//                                                   dataSize,
//                                                   GL_MAP_WRITE_BIT);
//    if (bufPtr) {
//        memcpy(bufPtr, data, static_cast<size_t>(dataSize));
//        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
//    }
//    // 将pbo缓冲区中的数据拷贝到纹理,调用 glTexSubImage2D 后立刻返回,不影响 CPU 时钟周期
//    // 这个函数会判断 GL_PIXEL_UNPACK_BUFFER 这个中央有没有绑定一个缓冲区
//    //   如果有,就从这个缓冲区读取数据,而不是data参数指定的那个内存
//    // 这样glTexSubImage2D就会从咱们的缓冲区中读取数据了
//    // 这里为什么要用glTexSubImage2D呢,因为如果用glTexImage2D,glTexImage2D会销毁纹理内存从新申请,glTexSubImage2D就仅仅只是更新纹理中的数据,这就进步了速度,并且优化了显存的利用率
//    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
//    // Pbo解除
//    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
//    // 解绑定
//    glBindTexture(GL_TEXTURE_2D, 0);
//}



// 双PBO测试
void PBOOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel  uploadPboIndex:%d",uploadPboIndex);
    imageWidth = width;
    imageHeight = height;
    // Pbo初始化
    initPboArray();

    if(imageTextureId == 0){

        glGenTextures(1, &imageTextureId);

        // 激活纹理,留神以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
        // 默认是0,如果不是0的话,须要在onDraw的时候从新激活一下?
//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一样的
        glActiveTexture(GL_TEXTURE2);
        glUniform1i(textureSampler, 2);

        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, imageTextureId);
        // 为以后绑定的纹理对象设置盘绕、过滤形式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // pixels参数传递空,前面会通过pbo更新纹理数据
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

        // 生成mip贴图
        glGenerateMipmap(GL_TEXTURE_2D);
    }

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, imageTextureId);

    int dataSize = width * height * 4;
    // 应用Pbo
    // 指针运算你要会一点呀
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, *(uploadPboIds + uploadPboIndex % NUM_PBO ));
    // 将纹理数据拷贝进入缓冲区
    GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
                                                   dataSize,
                                                   GL_MAP_WRITE_BIT);
    if (bufPtr) {
        memcpy(bufPtr, data, static_cast<size_t>(dataSize));
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }
    // 将pbo缓冲区中的数据拷贝到纹理,调用 glTexSubImage2D 后立刻返回,不影响 CPU 时钟周期
    // 这个函数会判断 GL_PIXEL_UNPACK_BUFFER 这个中央有没有绑定一个缓冲区
    //   如果有,就从这个缓冲区读取数据,而不是data参数指定的那个内存
    // 这样glTexSubImage2D就会从咱们的缓冲区中读取数据了
    // 这里为什么要用glTexSubImage2D呢,因为如果用glTexImage2D,glTexImage2D会销毁纹理内存从新申请,glTexSubImage2D就仅仅只是更新纹理中的数据,这就进步了速度,并且优化了显存的利用率
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    // Pbo解除
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
    // 索引自加
    uploadPboIndex++;
}

//// 单PBO读取测试
//void PBOOpengl::readPixel(uint8_t **data,int *width,int *height) {
//
//    *width = eglHelper->viewWidth;
//    *height = eglHelper->viewHeight;
//    int dataSize = eglHelper->viewWidth * eglHelper->viewHeight * 4;
//    *data = new uint8_t[dataSize];
//    // 办法一 失常的glReadPixels读取 最简略
////    glReadPixels(0, 0, eglHelper->viewWidth, eglHelper->viewHeight,
////                 GL_RGBA, GL_UNSIGNED_BYTE, *data);
//
//    // 办法二 pbo读取
//    // 首先咱们要把缓冲区绑定到 GL_PIXEL_PACK_BUFFER 这个中央
//    glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
//    // 重新分配一下空间
//    glBufferData(GL_PIXEL_PACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
//    // 这个函数会判断 GL_PIXEL_PACK_BUFFER 这个中央有没有绑定一个缓冲区,如果有,那就把数据写入到这个缓冲区里
//    // 前4个参数就是要读取的屏幕区域,不多解释
//    //  格局是RGB,类型是BYTE,每个像素1字节
//    // 如果GL_PIXEL_PACK_BUFFER有绑定缓冲区,最初一个参数就作为偏移值来应用,传nullptr就行
//    glReadPixels(0, 0, *width, *height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
//    // 好了咱们曾经胜利把屏幕的像素数据复制到了缓冲区里
//
//    // 这时候,你能够用 glMapBuffer 失去缓冲区的内存指针,来读取外面的像素数据,保留到图片文件
//    // 留神glMapBuffer的第1个参数不肯定要是GL_PIXEL_PACK_BUFFER,你能够把缓冲区绑定到比方下面init函数的GL_ARRAY_BUFFER
//    //  而后这里也传GL_ARRAY_BUFFER,因为懒得再绑定一次,就接着用下面绑定的GL_PIXEL_PACK_BUFFER吧
//    GLubyte *mapPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dataSize,
//                                                              GL_MAP_READ_BIT));
//    if (nullptr != mapPtr)
//    {
//        LOGD("readPixel 数据拷贝");
//        // 拷贝数据
//        memcpy(*data,mapPtr,dataSize);
//        // 解除Map
//        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
//    } else{
//        LOGD("readPixel glMapBufferRange null");
//    }
//    // 完事了把GL_PIXEL_PACK_BUFFER这个中央的缓冲区解绑掉,免得别的函数误操作
//    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
//}

// 双PBO读取测试
void PBOOpengl::readPixel(uint8_t **data,int *width,int *height) {

    *width = eglHelper->viewWidth;
    *height = eglHelper->viewHeight;
    int dataSize = eglHelper->viewWidth * eglHelper->viewHeight * 4;
    *data = new uint8_t[dataSize];

    // 首先咱们要把缓冲区绑定到 GL_PIXEL_PACK_BUFFER 这个中央
    // 指针运算你要会一点呀
    glBindBuffer(GL_PIXEL_PACK_BUFFER, *(downloadPboIds + downloadPboIndex % NUM_PBO));
    // 重新分配一下空间
    glBufferData(GL_PIXEL_PACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
    // 这个函数会判断 GL_PIXEL_PACK_BUFFER 这个中央有没有绑定一个缓冲区,如果有,那就把数据写入到这个缓冲区里
    // 前4个参数就是要读取的屏幕区域,不多解释
    //  格局是RGB,类型是BYTE,每个像素1字节
    // 如果GL_PIXEL_PACK_BUFFER有绑定缓冲区,最初一个参数就作为偏移值来应用,传nullptr就行
    glReadPixels(0, 0, *width, *height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    // 好了咱们曾经胜利把屏幕的像素数据复制到了缓冲区里

    // 这时候,你能够用 glMapBuffer 失去缓冲区的内存指针,来读取外面的像素数据,保留到图片文件
    // 留神glMapBuffer的第1个参数不肯定要是GL_PIXEL_PACK_BUFFER,你能够把缓冲区绑定到比方下面init函数的GL_ARRAY_BUFFER
    //  而后这里也传GL_ARRAY_BUFFER,因为懒得再绑定一次,就接着用下面绑定的GL_PIXEL_PACK_BUFFER吧
    GLubyte *mapPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dataSize,
                                                              GL_MAP_READ_BIT));
    if (nullptr != mapPtr)
    {
        LOGD("readPixel 数据拷贝");
        // 拷贝数据
        memcpy(*data,mapPtr,dataSize);
        // 解除Map
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    } else{
        LOGD("readPixel glMapBufferRange null");
    }
    // 完事了把GL_PIXEL_PACK_BUFFER这个中央的缓冲区解绑掉,免得别的函数误操作
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    // Pbo索引自加
    downloadPboIndex++;
}

运行后果:

通过点击按钮能够实现应用双PBO的形式下载纹理数据。

对于PBO的坑

以下是笔者在网上看到某博主总结的对于PBO的坑,因为设施无限,笔者也没有进行过严格的比照测试,这里援用原文仅供记录参考:

然而PBO还有一个十分坑的中央,经测试表明,在局部硬件上glMapBufferRange映射进去的Buffer拷贝极为耗时,能够高达30+ms,这对于音视频解决显然是不能承受的。通常,映射进去的是一个DirectByteBuffer,也是一个堆外内存(C内存),这部分内存自身只能通过Buffer.get(byte[])拷贝来拿到数据,但失常状况下只须要2-3ms。呈现这种问题预计是硬件上留下的坑。
所以,在Android上应用PBO是有比拟多的兼容性问题的,包含下面说的。正确应用PBO的形式是,首先判断是否反对PBO,如果反对,则还是先应用glReadPixels进行读取测试,记录均匀耗时,而后再应用PBO进行读取测试,记录均匀耗时,最初比照两个形式的耗时,抉择最快的一个。这样动静解决是比较复杂的,然而在这种状况下你不得不这样做。那么有没有一种既简略又高效的形式呢?

参考

http://www.songho.ca/opengl/g…

往期笔记

Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形绘制
Opengl ES之四边形绘制
Opengl ES之纹理贴图
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO

关注我,一起提高,人生不止coding!!!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理