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的方式读取像素

03-28 16:30