天天看點

Android OpenGL 渲染圖像讀取哪家強glReadPixelsPBO(Pixel Buffer Object)ImageReaderHardwareBuffer實測對比聯系與交流

作者:位元組流動

來源:

https://blog.csdn.net/Kennethdroid/article/details/109339906

glReadPixels

glReadPixels 是 OpenGL ES 的 API ,OpenGL ES 2.0 和 3.0 均支援。 使用非常友善,下面一行代碼即可搞定,但是效率也是最低的。

glReadPixels(0, 0, outImage.width, outImage.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);      

當調用 glReadPixels 時,首先會影響 CPU 時鐘周期,同時 GPU 會等待目前幀繪制完成,讀取像素完成之後,才開始下一幀的計算,造成渲染管線停滞。

值得注意的是 glReadPixels 讀取的是目前綁定 FBO 的顔色緩沖區圖像,是以當使用多個 FBO(幀緩沖區對象)時,需要确定好我們要讀那個 FBO 的顔色緩沖區。

glReadPixels 性能瓶頸一般出現在大分辨率圖像的讀取,是以目前通用的優化方法是在 shader 中将處理完成的 RGBA 轉成 YUV (一般是 YUYV),然後基于 RGBA 的格式讀出 YUV 圖像,這樣傳輸資料量會降低一半,性能提升明顯。

PBO(Pixel Buffer Object)

PBO 是 OpenGL ES 3.0 的概念,稱為像素緩沖區對象,主要被用于異步像素傳輸操作。 PBO 僅用于執行像素傳輸,不連接配接到紋理,且與 FBO (幀緩沖區對象)無關。

PBO 類似于 VBO(頂點緩沖區對象),PBO 開辟的也是 GPU 緩存,而存儲的是圖像資料。

PBO 可以在 GPU 的緩存間快速傳遞像素資料,不影響 CPU 時鐘周期,除此之外,PBO 還支援異步傳輸。

PBO 類似于“以空間換時間”政策,在使用一個 PBO 的情況下,性能無法有效地提升,通常需要多個 PBO 交替配合使用。

Android OpenGL 渲染圖像讀取哪家強glReadPixelsPBO(Pixel Buffer Object)ImageReaderHardwareBuffer實測對比聯系與交流

如上圖所示,利用 2 個 PBO 從幀緩沖區讀回圖像資料,使用 glReadPixels 通知 GPU 将圖像資料從幀緩沖區讀回到 PBO1 中,同時 CPU 可以直接處理 PBO2 中的圖像資料。

關于 PBO 的詳細使用可以參考文章:

OpenGL ES 3.0 開發連載(22):PBO

, 這裡不再贅述。

ImageReader

ImageReader 是 Android SDK 提供的 Java 層對象,其内部會建立一個 Surface 對象。

常用于 Android Camera2.0 相機預覽,通過 addTarget 将 Surface 對象作為相機預覽圖像的輸出載體,通過回調接口擷取預覽圖像。

mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 2);

mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

mSurface = mImageReader.getSurface();

private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        if (image != null) {
            //處理相機預覽圖像 image
            image.close();
        }
    }
};      

那麼 ImageReader 怎麼跟 OpenGL ES 結合使用呢?

我們知道利用 EGL 建立 OpenGL 上下文環境時,eglCreateWindowSurface 需要傳入 ANativeWindow 對象,而 ANativeWindow 又基于 Surface 對象建立的。

那我們可以利用 ImageReader 對象的 Surface 對象作為 OpenGL 展示渲染結果的 Window Surface ,每次渲染的結果可以通過 ImageReader 對象的回調擷取。

代碼可以參考:

https://github.com/a422070876/OpenGLESToBitmap

HardwareBuffer

HardwareBuffer 是一個更底層的對象,代表可由各種硬體單元通路的緩沖區。特别地,HardwareBuffer 可以映射到各種硬體系統的存儲器,例如 GPU 、 傳感器或上下文集線器或其他輔助處理單元。

HardwareBuffer 是 Android 8 API >= 26 提供的用于替換 GraphicBuffer 的接口,在 API <= 25 時可以使用 GraphicBuffer ,兩者在使用步驟上基本一緻,均可以用于快速讀取顯存(紋理)圖像資料,但是 HardwareBuffer 還可以通路其他硬體的存儲器,使用更廣泛。

Android 在 Native 層和 Java 層均提供了 HardwareBuffer 實作接口,其中 Native 層叫 AHardwareBuffer 。

AHardwareBuffer 讀取顯存(紋理)圖像資料時,需要與 GLEXT 和 EGLEXT 配合使用 ,主要步驟:首先需要建立 AHardwareBuffer 和 EGLImageKHR 對象,然後将目标紋理(FBO 的顔色附着)與 EGLImageKHR 對象綁定,渲染結束之後便可以讀取紋理圖像。

HardwareBuffer 讀取紋理圖像資料:

unsigned char *ptrReader = nullptr;
AHardwareBuffer_lock(m_HwBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, nullptr,
                     (void **) &ptrReader);
memcpy(dstBuffer, ptrReader, imgWidth * imgHeight * 3 / 2);//直接可以讀取 YUV 圖像(NV21)
int32_t fence = -1;
AHardwareBuffer_unlock(m_PHwBuffer, &fence);      

另外,HardwareBuffer 支援直接讀取紋理中的 YUV (YUV420)格式的圖像,隻需要在 shader 中實作 RGB 到 YUV 的格式轉換。

GLES 3.0 YUV 擴充直接支援 RGB 到 YUV 的轉換、。

#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
in vec2 v_texCoord;
layout(yuv) out vec4 outColor;
uniform sampler2D s_texture;
void main()
{
    //色彩空間标準公式
    yuvCscStandardEXT conv_standard = itu_601_full_range;

    vec4 rgbaColor = texture(s_texture, v_texCoord);
    //dealwith rgba
    
    vec3 rgbColor = rgbaColor.rgb;
    vec3 yuv = rgb_2_yuv(rgbColor, conv_standard);//實作 RGB 到 YUV 的格式轉換
    outColor = vec4(yuv, 1.0);
}      

HardwareBuffer 和 GraphicBuffer 具體使用可以參考:

https://github.com/fuyufjh/GraphicBuffer

實測對比

通過在 Android 9(SDM8150)手機上,對比讀取相同格式 3k 左右分辨率圖像的性能,其中 ImageReader、 PBO 和 HardwareBuffer 明顯優于 glReadPixels 方式,HardwareBuffer、 ImageReader 以及 PBO 三種方式性能相差不大,但是理論上 HardwareBuffer 性能最優。

四種方式中,glReadPixels 使用最友善,HardwareBuffer 實作最複雜,實作複雜度:HardwareBuffer > PBO > ImageReader > glReadPixels 。

結合實測性能和實作難度,Native 層建議選擇 PBO 方式,超大分辨率建議嘗試 HardwareBuffer 方式,Java 層建議使用 ImageReader 方式。

聯系與交流

技術交流擷取源碼可以添加我的微信:Byte-Flow ,拉你進 OpenGL ES 技術交流群。

「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
Android OpenGL 渲染圖像讀取哪家強glReadPixelsPBO(Pixel Buffer Object)ImageReaderHardwareBuffer實測對比聯系與交流

繼續閱讀