Android OpenGL 渲染圖像讀取哪家強?


glReadPixels

glReadPixels 是 OpenGL ES 的 API ,OpenGL ES 2.0 和 3.0 均支持。使用很是方便,下面一行代碼便可搞定,可是效率很低。git


glReadPixels(0, 0, outImage.widthoutImage.heightGL_RGBAGL_UNSIGNED_BYTEbuffer);


當調用 glReadPixels 時,首先會影響 CPU 時鐘週期,同時 GPU 會等待當前幀繪製完成,讀取像素完成以後,纔開始下一幀的計算,形成渲染管線停滯github


值得注意的是 glReadPixels 讀取的是當前綁定 FBO 的顏色緩衝區圖像,因此當使用多個 FBO(幀緩衝區對象)時,須要肯定好咱們要讀那個 FBO 的顏色緩衝區。web


glReadPixels 性能瓶頸通常出如今大分辨率圖像的讀取,因此目前通用的優化方法是在 shader 中將處理完成的 RGBA 轉成 YUV (通常是 YUYV 格式),而後基於 RGBA 的格式讀出 YUV 圖像,這樣傳輸數據量會下降一半,性能提高明顯。緩存


PBO

PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念,稱爲像素緩衝區對象,主要被用於異步像素傳輸操做。PBO 僅用於執行像素傳輸,不鏈接到紋理,且與 FBO (幀緩衝區對象)無關。微信


PBO 相似於 VBO(頂點緩衝區對象),PBO 開闢的也是 GPU 緩存,而存儲的是圖像數據。app


PBO 能夠在 GPU 的緩存間快速傳遞像素數據,不影響 CPU 時鐘週期,除此以外,PBO 還支持異步傳輸。異步


PBO 相似於「以空間換時間」策略,在使用一個 PBO 的狀況下,性能沒法有效地提高,一般須要多個 PBO 交替配合使用。編輯器


2 個 PBO read pixels

如上圖所示,利用 2 個 PBO 從幀緩衝區讀回圖像數據,使用 glReadPixels 通知 GPU 將圖像數據從幀緩衝區讀回到 PBO1 中,同時 CPU 能夠直接處理 PBO2 中的圖像數據。ide


關於 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 對象的回調獲取。


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, -1nullptr,(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


實測性能對比

經過在 SDM8150手機上,對比讀取相同格式 3k 左右分辨率圖像的性能,其中 ImageReader、 PBO 和 HardwareBuffer 明顯優於 glReadPixels 方式。


HardwareBuffer、 ImageReader 以及 PBO 三種方式性能相差不大,可是理論上 HardwareBuffer 性能最優。


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


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




-- END --


進技術交流羣,掃碼添加個人微信:Byte-Flow



獲取視頻教程和源碼



推薦:

字節流動 OpenGL ES 技術交流羣來啦

FFmpeg + OpenGL ES 實現 3D 全景播放器

FFmpeg + OpenGLES 實現視頻解碼播放和視頻濾鏡

一文掌握 YUV 圖像的基本處理

Android OpenGL ES 從入門到精通系統性學習教程

OpenGL ES 實現動態(水波紋)漣漪效果


以爲不錯,點個在看唄~

本文分享自微信公衆號 - 字節流動(google_developer)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索