Android opengles深入渲染优化

0 阅读6分钟

这是一篇关于Android opengles深入优化的经验

1.如果有视频和图片结合一起的情况,例如滤镜遮罩等场景。opengles的oes扩展是将mediadecode解码硬解到对应的纹理空间当中。硬解解析出来的是yuv数据,通过oes转化为rgba数据。 oes的视频纹理,和正常的图片纹理,是能够同时运用在同一个shader当中的。但是网上资料都不会提及到,都会让通过Framebuffer做一层数据缓存作为转换。这样会浪费一次渲染。

//初始化视频纹理
shared_ptr<TextureInfo> TextureLoadUtil::loadVideoTexture() {
    //shader
    GLuint textureId;
    glGenTextures(1,&textureId);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    auto textureInfo = make_shared<TextureInfo>();
    textureInfo->textureId = textureId;
    return textureInfo;
}

//初始化图片纹理
shared_ptr<TextureInfo> TextureLoadUtil::loadTexture(unsigned char *bitmap, AndroidBitmapInfo* info) {
    if (bitmap == nullptr) {
        YMLOGE("loadTexture bitmap = null");
        return nullptr;
    }

    GLuint textureObjectIds = 0;
    glGenTextures(1, &textureObjectIds);
    if (textureObjectIds == 0) {
        YMLOGE("loadTexture textureObjectIds = 0");
        return nullptr;
    }

    glBindTexture(GL_TEXTURE_2D, textureObjectIds);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //纹理放大缩小使用线性插值
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    //超出的部份会重复纹理坐标的边缘,产生一种边缘被拉伸的效果,s/t相当于x/y轴坐标
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);

    GLint format = GL_RGB;
    if (info->format == ANDROID_BITMAP_FORMAT_RGB_565) { //RGB三通道,例如jpg格式
        format = GL_RGB;
    } else if (info->format == ANDROID_BITMAP_FORMAT_RGBA_8888) {  //RGBA四通道,例如png格式 android图片是ARGB排列,所以还是要做图片转换的
        format = GL_RGBA;
    }
    //将图片数据生成一个2D纹理
    glTexImage2D(GL_TEXTURE_2D, 0, format, info->width, info->height, 0, format, GL_UNSIGNED_BYTE,
                 bitmap);

    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, 0);
    auto textureInfo = make_shared<TextureInfo>();
    textureInfo->width = info->width;
    textureInfo->height = info->height;
    textureInfo->textureId = textureObjectIds;
    return textureInfo;
}

//glsl里面
uniform sampler2D u_texture;
uniform samplerExternalOES v_texture;
//这样可以选择性获取到纹理
color = u_playType ? texture(v_texture, v_texcoord): texture(u_texture, v_texcoord);

//这里需要注意,激活纹理的时候(glActiveTexture),不能用同一个坑位,不然会导致无法展示
glActiveTexture(GL_TEXTURE0);
if (!sdInfo->isVideo) {
    glBindTexture(GL_TEXTURE_2D, sdInfo->srcTextureId);
}
// 纹理坐标
glUniform1i(uTextureLocation, 0);
glActiveTexture(GL_TEXTURE2);
if (sdInfo->isVideo) {
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, sdInfo->srcTextureId);
}
// 纹理坐标
glUniform1i(vTextureLocation, 2);

2.切换shader会有一定开销,如果不是shader逻辑非常多那么最好的做法,就是将shader的逻辑都做到同一个glsl当中。

那么当shader逻辑不断膨胀后,那么会出现问题呢?

会出现逻辑的分支会变得非常多,逻辑分支变多,那么整个shader运动的速度就会越来越慢

之后因为shader代码变多了,会被编译到gpu当中,会让gpu的占用不断上升。那听起来将会是灾难级的。

如何可以解决这种问题呢?

Android当中,glsl文件是可以通过字节拼接的,编译前就条件判断来选择必须加入到glsl字符串内逻辑。

string fs = R"(#version 300 es)";
fs.append("\n");
if (android_get_device_api_level() <= 23) {
    fs.append("#extension GL_OES_EGL_image_external : require");
} else {
    fs.append("#extension GL_OES_EGL_image_external_essl3 : require");
}
fs.append("\n");
fs.append(R"(
    precision mediump float;
    uniform sampler2D u_texture;
    uniform sampler2D u_dstTexture;
    uniform samplerExternalOES v_texture;
    )
);

3.framebuffer的创建是需要创建耗时的,在渲染当中去创建framebuffer不断的申请和销毁gpu空间,并不是好事。

良好的习惯,我们一定要提前创建好必须的framebuffer。

如果想要进一步利用好,我们可以对framebuffer创建缓存池或者通过交换framebuffer的指针地址的方法来,减少framebuffer的创建

frambuffer1.swap(framebuffer2);

framebuffer重新绑定后,可以进行纹理的viewport大小调整,以及是否清屏Framebuffer的操作。

4.如果确定计算矩阵的数据没有变化,我们可以选择性维持Framebuffer的纹理数据。

当然有一些特殊的方式不要进行维持纹理,如视频解码的纹理,一定不要维持framebuffer数据不清空,不然不会更新到视频数据。

5.Android硬解h264的mp4,mp4的长宽尺寸需要整取8,不然会出现显示位置偏移的情况。h265测试的时候,某些资源不会出现这种情况。不知道是否机型还是h265解码有优化。

6.计算加速,可以考虑使用arm_neon.h库进行计算加速,特别multipy这种计算,可以考虑使用

void Matrix::multiply(const Matrix &a, const Matrix &b, Matrix &dst) {
    Matrix::Matrix() {
        values[0] = 1;
        values[1] = 0;
        values[2] = 0;
        values[3] = 0;
        values[4] = 0;
        values[5] = 1;
        values[6] = 0;
        values[7] = 0;
        values[8] = 0;
        values[9] = 0;
        values[10] = 1;
        values[11] = 0;
        values[12] = 0;
        values[13] = 0;
        values[14] = 0;
        values[15] = 1;
    }

    const float* a_data = &a.values[0];
    const float* b_data = &b.values[0];
    float* dst_data = &dst[0];

    // 加载a的四个列向量
    float32x4x4_t a_cols = vld4q_f32(a_data);

    // 加载b的四行
    float32x4_t b_row0 = vld1q_f32(b_data);
    float32x4_t b_row1 = vld1q_f32(b_data + 4);
    float32x4_t b_row2 = vld1q_f32(b_data + 8);
    float32x4_t b_row3 = vld1q_f32(b_data + 12);

    // 计算dst的四行
    computeRow(a_cols,b_row0, dst_data);
    computeRow(a_cols, b_row1, dst_data + 4);
    computeRow(a_cols, b_row2, dst_data + 8);
    computeRow(a_cols, b_row3, dst_data + 12);
}


void Matrix::computeRow(float32x4x4_t a_cols, float32x4_t b_row, float *dst_row) {
    float32x4_t dot0 = vmulq_f32(b_row, a_cols.val[0]);
    float32x2_t sum0 = vpadd_f32(vget_high_f32(dot0), vget_low_f32(dot0));
    sum0 = vpadd_f32(sum0, sum0);
    dst_row[0] = vget_lane_f32(sum0, 0);

    float32x4_t dot1 = vmulq_f32(b_row, a_cols.val[1]);
    float32x2_t sum1 = vpadd_f32(vget_high_f32(dot1), vget_low_f32(dot1));
    sum1 = vpadd_f32(sum1, sum1);
    dst_row[1] = vget_lane_f32(sum1, 0);

    float32x4_t dot2 = vmulq_f32(b_row, a_cols.val[2]);
    float32x2_t sum2 = vpadd_f32(vget_high_f32(dot2), vget_low_f32(dot2));
    sum2 = vpadd_f32(sum2, sum2);
    dst_row[2] = vget_lane_f32(sum2, 0);

    float32x4_t dot3 = vmulq_f32(b_row, a_cols.val[3]);
    float32x2_t sum3 = vpadd_f32(vget_high_f32(dot3), vget_low_f32(dot3));
    sum3 = vpadd_f32(sum3, sum3);
    dst_row[3] = vget_lane_f32(sum3, 0);
}

计算速度是否有提升,还是需要看适配场景的,有可能并不会出现提升。

7.glsl减少if语句和判断,有些编译器会对三元运算符直接优化