关于opengl-es:Opengl-ES之PBO

51次阅读

共计 21658 个字符,预计需要花费 55 分钟才能阅读完成。

叨叨一句几句

对于 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!!!

正文完
 0