这是一篇关于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语句和判断,有些编译器会对三元运算符直接优化