Android OpenGL ES 3.0 Pixel Buffer Object使用

关于PBO,找了很多资料,然而google了很久,大部分的PBO资料都和这个类似https://blog.csdn.net/panda1234lee/article/details/51546502 。在上传texture的过程中,我分别试了1、2、3个PBO来进行上传,然而效率并没有增加,反而下低了,有点无法理解。然而在读取数据时候,使用两个PBO是可以提高效率,所以总的来说还是有一定研究价值的。

使用两个PBO减少glReadPixels的时间

我这里模仿了一下不断采集纹理,我每次用同一张图片去完整更新纹理,就好像每次都采集了一张图片。然后使用FBO进行中间处理,读取数据后复制出来到一个数组。总的来说就是模拟摄像头采集然后编码的流程吧。主要是为了避免过多的逻辑尽量减少额外的功能点。

首先先生成两个PBO,并且分配空间,这里我还生成了三个上传纹理的PBO,但是没有使用了,因为上传纹理没办法通过PBO提高效率,有知道的大佬可以告诉一下原因。

if (init) {
        init= false;
        uploadPobs = new GLuint[3];
        downloadPbos = new GLuint[2];
        glGenBuffers(3, uploadPobs);
        glGenBuffers(2, downloadPbos);
        int align = 128;
        int width = curPicWidth;
        int height = curPicHeight;
        mPboSize = ((width * 4 + (align - 1)) & ~(align - 1)) * height;
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPobs[0]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, mPboSize, 0, GL_STREAM_DRAW);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPobs[1]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, mPboSize, 0, GL_STREAM_DRAW);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPobs[2]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, mPboSize, 0, GL_STREAM_DRAW);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[0]);
        glBufferData(GL_PIXEL_PACK_BUFFER, mPboSize, 0, GL_STATIC_READ);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[1]);
        glBufferData(GL_PIXEL_PACK_BUFFER, mPboSize, 0, GL_STATIC_READ);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    }

接下来就是正常的OpenGL 的纹理绘制,我们将坐标绑定到VAO,然后创建FBO,并且绘制到FBO的纹理上。然后读取数据。最后我们再切换回屏幕,在绘制到屏幕上,这次看到的图形就是上下颠倒的了。之前我也试过不使用FBO直接每次更新单个纹理的整体数据来进行绘制,再使用PBO进行读取,但是无法提高效率。应该更新纹理的时候,发现异步DMA没有完成会等待,所以会导致纹理更新时间变长。所以还是需要使用FBO。

void PboRender::render() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glDisable(GL_DITHER);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, curPicWidth, curPicHeight);
    glUseProgram(program);
//    resetTexture();
    glBindFramebuffer(GL_FRAMEBUFFER, frame);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(textureLocation, 0);
    glBindVertexArray(VAO[0]);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    readPixels();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, _backingWidth, _backingHeight);
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glBindTexture(GL_TEXTURE_2D, textureFrame);
    glBindVertexArray(VAO[0]);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

}

接着下面就是读取数据,同样模拟一下,功能就是读取数据之后复制到另一个数组,在这个过程中PBO真的是个大坑。同样的代码在不同的设备上性能差距太大了。提一下我发现的点吧。

  • 首先看glReadPixels(),这个函数在使用PBO的时候,网上的资料都说会立刻返回,然后我实测依然会有等待时间。读取的byte数(也就是宽*高*4)不是128的倍数的时候,使用PBO和不使用几乎没有提升性能。而如果是128的倍数,这个时间几乎减少了5倍左右。应该是64位cpu的优化吧,也就是说如果我们录屏的时候分辨率设置设置得当,在使用软编的时候,是可以得到一些性能提升的。但是如果分辨率设置不当,考虑到后面需要复制数据,综合起来性能甚至会降低。(对于可能不是64位cpu的手机没有测试过)。
  • 接着就是复制数据时候的坑了,直接是memcpy()进行复制的时候,在不使用PBO的时候速度很快,但是在使用PBO的时候,我们再glMapBufferRange拿到对应数据的指针后,使用memcpy()会复制得特别慢,官方文档对这个API的描述也说了,可能会拿到不可缓存的内存区域,所以在读取的时候,性能会无法得到保证。再加上上一条说的时间,综合起来甚至不如不适用PBO,真的略坑。后来反复查找资料,使用ARM架构的NEON优化来拷贝数据,针对这种不可缓存的内存,在c++中使用汇编。测试下来,性能提升了很多。需要在build.gradle中开启NEON。
    在defaultConfig{}下添加。
 externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
                arguments '-DANDROID_ARM_NEON=TRUE'
            }
        }

复制数据的方法

void my_copy(volatile unsigned char *dst, volatile unsigned char *src, int sz)
{
    if (sz & 63) {
        sz = (sz & -64) + 64;
    }
    asm volatile (
    "NEONCopyPLD: \n"
            " VLDM %[src]!,{d0-d7} \n"
            " VSTM %[dst]!,{d0-d7} \n"
            " SUBS %[sz],%[sz],#0x40 \n"
            " BGT NEONCopyPLD \n"
    : [dst]"+r"(dst), [src]"+r"(src), [sz]"+r"(sz) : : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "cc", "memory");
}

读取数据方法

void PboRender::readPixels() {

    int size = mPboSize;
    char path[40];
    sprintf(path, "/mnt/sdcard/pixel/readPixel%d.rgba", picCount);
//    picCount--;
    if (picCount <= 0) {
        return;
    }
    if (!cachePixel) {
        cachePixel = new byte[size];
        memset(cachePixel, 0, size);
    }
//    if (access("/mnt/sdcard/pixel", 0)) {
//        mkdir("/mnt/sdcard/pixel", S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXG | S_IRWXO);
//    }
//    FILE *file = fopen(path, "wb");
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    long long curTime = getCurrentTime();
    if (downloadPboType == NONE) {
        long long cc = getCurrentTime();
        byte *pixel = new byte[size];
        glReadPixels(0, 0, curPicWidth, curPicHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
        LOGE("glReadPixels Time %lld", getCurrentTime() - cc);
        cc = getCurrentTime();
//        fwrite(pixel, size, 1, file);
        memcpy(cachePixel, pixel, size);
        LOGE("内存复制耗时 %lld", getCurrentTime() - cc);
        delete[] pixel;
    } else if (downloadPboType == ONE) {


    } else if (downloadPboType == TWO) {
        index = (index + 1) % 2;
        nextIndex = (index + 1) % 2;
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[index]);
        long long cc = getCurrentTime();
        glReadPixels(0, 0, curPicWidth, curPicHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        LOGE("glReadPixels Time %lld", getCurrentTime() - cc);

        if (readPixInit) {
            readPixInit = false;
            glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
            return;
        }
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[nextIndex]);
        GLubyte *ptr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0,
                                                               mPboSize,
                                                               GL_MAP_READ_BIT));
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // release pointer to mapping buffer
        cc = getCurrentTime();
        if (ptr) {
            my_copy(cachePixel,ptr,size);


        }
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

//            fwrite(ptr, size, 1, file);
        LOGE("内存复制耗时 %lld", getCurrentTime() - cc);

    }
//    fclose(file);

    LOGE("完成耗时%lld", getCurrentTime() - curTime);
}

好了,文章就到这里了,不是特别长,代码逻辑也比较简单,但是在性能测试上真的花了不少时间,PBO确实可以在一定条件下大幅提升性能,如果项目需要还是可以尝试一下,当然我们还可以在GPU内把数据转成YUV422,这样读取的时候时间还可以减少一半。总的来说性能应该提升比较大的,就是PBO需要OpenGL ES 3.0,在手机版本上需要适配。

源码

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Android OpenGL ES 3.0是一个强大的图形渲染API,用于开发Android平台上的高性能3D应用程序和游戏。从入门到精通OpenGL ES 3.0需要系统性的学习教程,以下是一个简要的学习路径: 1. 基础知识:首先需要了解OpenGL ES 3.0的基础知识,包括图形渲染管线、坐标系、顶点和片元着色器等。可以通过阅读相关的教程、书籍或者在线资源来获得这方面的知识。 2. 环境搭建:学习OpenGL ES 3.0之前,需要先搭建好学习环境。可以下载安装Android Studio和相关的开发工具,以及配置好OpenGL ES 3.0的开发环境。 3. 学习资源:寻找一些高质量的学习资源,如教程、书籍或者在线课程。可以选择一些经典的OpenGL ES 3.0教程,其中包括基础知识、实例代码和案例分析等。 4. 实践练习:学习OpenGL ES 3.0最重要的一点就是不断地进行实践练习。可以按照教程中的示例代码,逐步实现一些简单的图形渲染效果。通过实践来加深对OpenGL ES 3.0的理解,掌握各种绘制技术和渲染效果。 5. 深入研究:在掌握了基础知识和实践经验之后,可以进一步深入研究OpenGL ES 3.0的高级特性和扩展功能。包括纹理映射、着色器编程、光照和阴影效果等。可以参考一些专业书籍和高级教程来进一步提升自己的技术水平。 6. 项目实践:最后一步是通过实际项目的实践来巩固所学的知识。可以尝试开发一些简单的游戏或者应用程序,利用OpenGL ES 3.0来实现复杂的图形渲染效果。通过实际项目的经验,可以进一步提升自己的技术能力和解决问题的能力。 总之,学习Android OpenGL ES 3.0需要系统性的学习教程,并通过不断实践和项目实践来提升自己的技术水平。只有在不断学习和实践中,才能逐步精通OpenGL ES 3.0并运用到实际开发中。 ### 回答2: Android OpenGL ES 3.0 是一种强大的图形渲染技术,用于在Android设备上创建高性能的3D图形和特效。要系统地学习和掌握Android OpenGL ES 3.0,您可以按照以下步骤: 1. 学习基础知识:首先,您需要了解计算机图形学和OpenGL ES的基本概念。这包括了解3D图形渲染的原则、OpenGL ES的架构、状态机模型等。可以通过阅读相关的教材或者参考互联网上的优质教程来学习这些内容。 2. 编程环境设置:为了开始使用Android OpenGL ES 3.0,您需要配置开发环境。这包括安装和配置Android开发工具包(Android SDK)以及适当的集成开发环境(如Android Studio)。确保您的开发环境正确设置,并具备OpenGL ES 3.0的支持。 3. 学习OpenGL ES API:学习OpenGL ES 3.0的API是掌握该技术的关键。您需要理解OpenGL ES的基本绘图函数、顶点和片段着色器编程、纹理映射等概念。可以通过查阅OpenGL ES 3.0的官方文档或参考书籍来学习这些API。 4. 实践项目:通过实践项目来巩固所学的知识。您可以从最简单的项目开始,如画一个三角形,然后逐步扩展,添加更多的图形对象和特效。这样您可以深入了解OpenGL ES 3.0使用和性能优化。 5. 学习高级主题:一旦掌握了基础知识,您可以进一步学习OpenGL ES 3.0的高级主题。这可能包括光照、阴影、投影、深度测试和其他高级特性。这些主题的学习可以通过参考更高级的教程、专业书籍或者参与相关论坛和社区来深入研究。 6. 性能优化:了解如何优化OpenGL ES 3.0的性能也是非常重要的。您可以学习如何使用缓冲区对象、顶点缓冲区对象(VBO)、纹理压缩和其他优化技术来提高应用程序的帧率和响应速度。 总而言之,要系统学习和掌握Android OpenGL ES 3.0,您需要深入理解计算机图形学和OpenGL ES的原理,学习API的使用和性能优化技术,并通过实践项目来强化您的理解和应用能力。这需要坚持不懈的学习和实践,但通过这样的系统学习,您将能够成为一名Android OpenGL ES 3.0的专家。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值