Android使用OpenGL的时候要从GPU上获取绘制的像素一般都是使用glReadPixels,但是glReadPixels比较慢,特别是在低端设备上。在OpenGL ES 3.0之后也就是在Android7.0之后支持了PBO,PBO允许异步地将数据从CPU传输到GPU或从GPU传输到CPU,以提高性能并减少数据传输的延迟,但是在一些设备使用PBO后copy像素的耗时较长,并不能提速。
本文做的处理:先探测PBO与原始的glReadPixels,看谁耗时较少,谁耗时少使用谁,核心代码如下:
//FastGLReadPixels.h
#ifndef BZMEDIA_FASTGLREADPIXELS_H
#define BZMEDIA_FASTGLREADPIXELS_H
#include <jni.h>
#include <GLES3/gl3.h>
class FastGLReadPixels {
public:
FastGLReadPixels(JNIEnv *jniEnv, int width, int height);
jobject readPixels(JNIEnv *jniEnv, int x, int y);
void release(JNIEnv *jniEnv);
private:
static const int NUM_PBO = 2;
static const int LOG_STEP = 1;
jobject bitmap = nullptr;
int width = 0;
int height = 0;
int64_t logIndex = 0;
int64_t imgByteSize = 0;
unsigned char *rgbaData = nullptr;
int64_t downloadFboIndex = 0;
GLuint *downloadFboIds;
int64_t glReadPixelsBySrcTimeCost = 1;
int64_t glReadPixelsBySrcTimeIndex = 0;
int64_t glReadPixelsByPBOTimeCost = 1;
int64_t glReadPixelsByPBOTimeIndex = 0;
void initPbo();
int glReadPixelsBySrc(JNIEnv *jniEnv, int x, int y);
int glReadPixelsByPBO(JNIEnv *jniEnv, int x, int y);
};
#endif //BZMEDIA_FASTGLREADPIXELS_H
//FastGLReadPixels.cpp
//
/**
*Created by bookzhan on 2024−03-07 22:18.
*description:
*/
//
#include "FastGLReadPixels.h"
#include "BZLogUtil.h"
#include <utils/BitmapUtil.h>
#include <common/bz_time.h>
#include <android/bitmap.h>
#include <string.h>
FastGLReadPixels::FastGLReadPixels(JNIEnv *jniEnv, int width, int height) {
if (width > 0 & height > 0) {
bitmap = BitmapUtil::newGlobalRefBitmap(jniEnv, width, height);
} else {
BZLogUtil::logE("FastGLReadPixels width<=0||height<=0");
}
this->width = width;
this->height = height;
imgByteSize = width * height * 4;
rgbaData = new unsigned char[imgByteSize];
initPbo();
downloadFboIndex = 0;
}
void FastGLReadPixels::initPbo() {
downloadFboIds = new GLuint[NUM_PBO];
for (int i = 0; i < NUM_PBO; ++i) {
glGenBuffers(1, &downloadFboIds[i]);
// 绑定pbo
glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadFboIds[i]);
// 设置pbo内存大小
// 这一步十分重要,第2个参数指定了这个缓冲区的大小,单位是字节,一定要注意
// 然后第3个参数是初始化用的数据,如果你传个内存指针进去,这个函数就会把你的数据复制到缓冲区里,
// 我们这里一开始并不需要什么数据,所以传个nullptr就行了
glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, nullptr, GL_STREAM_READ);
// 解除绑定
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
BZLogUtil::logD("downloadPboId=%d", downloadFboIds[i]);
}
}
jobject FastGLReadPixels::readPixels(JNIEnv *jniEnv, int x, int y) {
if (nullptr == bitmap || nullptr == downloadFboIds) {
return nullptr;
}
//先试探
if (glReadPixelsBySrcTimeIndex < 3) {
int64_t startTime = getCurrentTime();
glReadPixelsBySrc(jniEnv, x, y);
glReadPixelsBySrcTimeIndex++;
int64_t timeCost = (getCurrentTime() - startTime);
glReadPixelsBySrcTimeCost += timeCost;
BZLogUtil::logD("test glReadPixelsBySrc timeCost=%lld", timeCost);
} else if (glReadPixelsByPBOTimeIndex < 3) {
int64_t startTime = getCurrentTime();
glReadPixelsByPBO(jniEnv, x, y);
glReadPixelsByPBOTimeIndex++;
int64_t timeCost = (getCurrentTime() - startTime);
glReadPixelsByPBOTimeCost += timeCost;
BZLogUtil::logD("test glReadPixelsByPBO timeCost=%lld", timeCost);
} else if (glReadPixelsBySrcTimeCost / glReadPixelsBySrcTimeIndex < glReadPixelsByPBOTimeCost / glReadPixelsByPBOTimeIndex) {
//谁值小用谁
glReadPixelsBySrc(jniEnv, x, y);
if (logIndex % LOG_STEP == 0) {
BZLogUtil::logV("glReadPixelsBySrc");
}
} else {
glReadPixelsByPBO(jniEnv, x, y);
if (logIndex % LOG_STEP == 0) {
BZLogUtil::logV("glReadPixelsByPBO");
}
}
logIndex++;
return bitmap;
}
int FastGLReadPixels::glReadPixelsBySrc(JNIEnv *jniEnv, int x, int y) {
//原始方法获取像素值
void *targetPixels;
int ret = AndroidBitmap_lockPixels(jniEnv, bitmap, &targetPixels);
if (ret < 0) {
BZLogUtil::logE("gifDataCallBack AndroidBitmap_lockPixels() targetPixels failed ! error=%d", ret);
return -1;
}
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, targetPixels);
AndroidBitmap_unlockPixels(jniEnv, bitmap);
return 0;
}
int FastGLReadPixels::glReadPixelsByPBO(JNIEnv *jniEnv, int x, int y) {
//PBO方法
void *targetPixels;
int ret = AndroidBitmap_lockPixels(jniEnv, bitmap, &targetPixels);
if (ret < 0) {
BZLogUtil::logE("gifDataCallBack AndroidBitmap_lockPixels() targetPixels failed ! error=%d", ret);
return -1;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadFboIds[downloadFboIndex % NUM_PBO]);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
GLubyte *mapPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, imgByteSize, GL_MAP_READ_BIT));
if (nullptr != mapPtr) {
memcpy(targetPixels, mapPtr, imgByteSize);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
} else {
ret = -1;
BZLogUtil::logW("readPixel glMapBufferRange null");
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
AndroidBitmap_unlockPixels(jniEnv, bitmap);
downloadFboIndex++;
return ret;
}
void FastGLReadPixels::release(JNIEnv *jniEnv) {
if (nullptr != bitmap) {
BitmapUtil::releaseGlobalRefBitmap(jniEnv, bitmap);
bitmap = nullptr;
}
if (nullptr != rgbaData) {
delete[](rgbaData);
rgbaData = nullptr;
}
if (nullptr != downloadFboIds) {
glDeleteBuffers(NUM_PBO, downloadFboIds);
delete[](downloadFboIds);
downloadFboIds = nullptr;
}
}
extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_opengl_FastGLReadPixels_init(JNIEnv *env, jclass clazz, jint image_width, jint image_height) {
return reinterpret_cast<jlong>(new FastGLReadPixels(env, image_width, image_height));
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_luoye_bzmedia_opengl_FastGLReadPixels_readPixels(JNIEnv *env, jclass clazz, jlong native_handle, jint start_x, jint start_y) {
if (native_handle != 0) {
FastGLReadPixels *pFastGlReadPixels = reinterpret_cast<FastGLReadPixels *>(native_handle);
return pFastGlReadPixels->readPixels(env, start_x, start_y);
}
return nullptr;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_luoye_bzmedia_opengl_FastGLReadPixels_release(JNIEnv *env, jclass clazz, jlong native_handle) {
if (native_handle != 0) {
FastGLReadPixels *pFastGlReadPixels = reinterpret_cast<FastGLReadPixels *>(native_handle);
pFastGlReadPixels->release(env);
delete pFastGlReadPixels;
}
}
#include "BitmapUtil.h"
jobject BitmapUtil::newGlobalRefBitmap(JNIEnv *jniEnv, int width, int height) {
jobject newBitmap = nullptr;
newLocalRefBitmap(jniEnv, &newBitmap, width, height);
return jniEnv->NewGlobalRef(newBitmap);
}
void BitmapUtil::releaseGlobalRefBitmap(JNIEnv *jniEnv, jobject bitmapGlobalRef) {
auto newBitmapGlobalRef = reinterpret_cast<jobject>(bitmapGlobalRef);
jclass bitmapClass = jniEnv->FindClass("android/graphics/Bitmap");
jmethodID isRecycledMethod = jniEnv->GetMethodID(bitmapClass, "isRecycled", "()Z");
jboolean isRecycled = jniEnv->CallBooleanMethod(newBitmapGlobalRef, isRecycledMethod);
if (!isRecycled) {
jmethodID recycleMethod = jniEnv->GetMethodID(bitmapClass, "recycle", "()V");
jniEnv->CallVoidMethod(newBitmapGlobalRef, recycleMethod);
}
jniEnv->DeleteGlobalRef(newBitmapGlobalRef);
}
void BitmapUtil::newLocalRefBitmap(JNIEnv *jniEnv, jobject *bitmap, int width, int height) {
jclass bitmapCls = jniEnv->FindClass("android/graphics/Bitmap");
jmethodID createBitmapFunctionMethodID = jniEnv->GetStaticMethodID(bitmapCls, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jstring configName = jniEnv->NewStringUTF("ARGB_8888");
jclass bitmapConfigClass = jniEnv->FindClass("android/graphics/Bitmap$Config");
jmethodID valueOfBitmapConfigFunctionMethodID = jniEnv->GetStaticMethodID(bitmapConfigClass, "valueOf",
"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
jobject bitmapConfigObj = jniEnv->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunctionMethodID, configName);
*bitmap = jniEnv->CallStaticObjectMethod(bitmapCls, createBitmapFunctionMethodID, width, height, bitmapConfigObj);
jniEnv->DeleteLocalRef(bitmapCls);
jniEnv->DeleteLocalRef(configName);
jniEnv->DeleteLocalRef(bitmapConfigObj);
jniEnv->DeleteLocalRef(bitmapConfigClass);
}
结论:经过Pixels,VIVO,SAMSUNG,XIAOMI,OPPO多设备,多机型测FBO的方案更加耗时,而且需要升级到OpenGLES 3 因此建议直接采用原始glReadPixels的方式读取像素