【原始碼解讀】Android Opengl OES 紋理怎麼渲染到 GL_TEXTURE_2D?

南方吳彥祖_藍斯發表於2021-09-29

背景

在客戶端中存在一種應用場景:需要將 MediaCodec 或者 Camera 產生的影像,透過 OpenGL 交給演算法做特效,由於演算法可能是基於普通的 Texture2D 紋理實現的,而 Android 上更常用的則是 GL_TEXTURE_EXTERNAL_OES 紋理,演算法一般都是基於 OpenGL 而不是 OpenGLES 環境實現的,所以就需要客戶端這邊做一個轉換工作。 【原始碼解讀】Android Opengl OES 紋理怎麼渲染到 GL_TEXTURE_2D?

這個轉換工作當然最好是在 GPU 中能完成的,因為如果透過 CPU 從 OES 紋理中讀出影像資料,再提交到 2D 紋理中,這一來一回,即浪費 CPU 頁佔有了記憶體,很不划算。所以就出現了這篇文章,如何利用 OpenGL 將 OES 紋理渲染到普通 2D 紋理上。

GL_TEXTURE_EXTERNAL_OES 紋理

外部 GLES 紋理 (GL_TEXTURE_EXTERNAL_OES) 與傳統 GLES 紋理 (GL_TEXTURE_2D) 的區別如下:

  • 外部紋理直接在從 BufferQueue 接收的資料中渲染紋理多邊形。

  • 外部紋理渲染程式的配置與傳統的 GLES 紋理渲染程式不同。

  • 外部紋理不一定可以執行所有傳統的 GLES 紋理活動。

外部紋理的主要優勢是它們能夠直接從 BufferQueue 資料進行渲染。在 Android 平臺上,BufferQueue 是連線圖形資料生產方和消費方的佇列,也就表示 OES 紋理能直接拿到某些生產方產生的圖形資料進行渲染。

OES Texture 渲染到 TEXTURE_2D

比如現在有個需求:使用 MediaCodec 解碼影片,最終需要將解碼的每一幀渲染到外部設定的一個 TEXTURE_2D 紋理上。

實現方案:MediaCodec 支援將解碼結果輸出到 Surface 中,我們可以透過構造一個繫結了 OES 紋理的 SurfaceTexture 來為 MediaCodec 構造一個輸出 Surface。當解碼結果寫入到 Surface 的 BufferQueue 之後,再利用 SurfaceTexture 將結果從 BufferQueue 渲染到 OES 紋理上,然後再透過 OpegGL 管道流水線操作將 OES 紋理上的內容渲染到 TEXTURE_2D 紋理:

MediaCodec 解碼到 Surface 虛擬碼如下:

oesTextureId = x

sTexture = SurfaceTexture(oesTextureId)
outputSurface = Surface(sTexture)
decoder.setOutputSurface(outputSurface)
複製程式碼

這裡可以借鑑 grafika 中 Buffer 的生成和消費流程:

【原始碼解讀】Android Opengl OES 紋理怎麼渲染到 GL_TEXTURE_2D?

然後在參考了 grafika 的流程後設計的流程:

【原始碼解讀】Android Opengl OES 紋理怎麼渲染到 GL_TEXTURE_2D?

正如上圖所示, 從 TextureOES 到 Texture2D 的關鍵是利用 FBO(幀緩衝) 。在執行 OpenGL 渲染之前,開始 FBO,渲染完成之後關閉 FBO。

幀緩衝實現

如果我們不額外設定 OpenGL 的幀緩衝,OpenGL 所有操作都將在預設幀緩衝的渲染緩衝上進行;如果我們啟用了自己的幀緩衝,也就是在繫結到 GL_FRAMEBUFFER 目標之後,所有的讀取和寫入幀緩衝的操作將會影響當前繫結的幀緩衝。

所以這裡的操作是:建立一個幀緩衝,將 Texture2D 紋理作為它的顏色緩衝,然後在利用 Shader 從 TextureOES 紋理上取樣之前將這個幀緩衝設定為 OpenGL 上下文當前啟用的幀緩衝。這樣設定之後就相當於,將 TextureOES 取樣到幀緩衝中,而幀緩衝背後又是 Texture2D,就間接的將 TextureOES 取樣到了 Texture2D 上。




class DecodeFBO {

   private var mFrameBuffer = -1

   init {
       val tmp = IntArray(1)
       GLES30.glGenFramebuffers(1, tmp, 0)
       SLGLUtils.checkGlError("glGenFrameBuffer")
       mFrameBuffer = tmp[0]
   }

   /**
    * 繫結 FBO 到 Texture2D 紋理
    */
   fun begin(texture2D: Int) {
       GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBuffer)
       SLGLUtils.checkGlError("glBindFrameBuffer")

       //將紋理作為幀緩衝物件的顏色緩衝
       GLES30.glFramebufferTexture2D(
           GLES30.GL_FRAMEBUFFER,
           GLES30.GL_COLOR_ATTACHMENT0,
           GLES30.GL_TEXTURE_2D,
           texture2D,
           0
       )
       checkGlError("glFramebufferTexture2D")
       val status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER)
       if (status != GLES30.GL_FRAMEBUFFER_COMPLETE) {
           Log.e(TAG, "bind FBO failed!")
           return
       }
   }

   fun end() {
       GLES30.glFramebufferTexture2D(
           GLES30.GL_FRAMEBUFFER,
           GLES30.GL_COLOR_ATTACHMENT0,
           GLES30.GL_TEXTURE_2D,
           0,
           0
       )
       checkGlError("detach texture from FBO")
       GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)
       checkGlError("deactivate FBO")
   }

   fun release() {
       GLES30.glDeleteFramebuffers(1, IntArray(1) { mFrameBuffer }, 0)
       checkGlError("glDeleteFramebuffers")
   }
}
複製程式碼

著色器實現

這裡的著色器就不復雜了,就是從一個紋理上取樣,然後設定給 gl_FragColor

頂點著色器:

private static final String VERTEX_SHADER =

       "uniform mat4 uMVPMatrix;\n" +
               "attribute vec4 aPosition;\n" +
               "attribute vec4 aTextureCoord;\n" +
               "varying vec2 vTextureCoord;\n" +
               "void main() {\n" +
               "  gl_Position = uMVPMatrix * aPosition;\n" +
               "  vTextureCoord = aTextureCoord.xy;\n" +
               "}\n";
複製程式碼

片段著色器:

private static final String FRAGMENT_SHADER =

       "#extension GL_OES_EGL_image_external : require\n" +
               "precision mediump float;\n" +
               "varying vec2 vTextureCoord;\n" +
               "uniform sampler2D sTexture;\n" +
               "void main() {\n" +
               "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
               "}\n";

作者:StefanJi

連結:

來源:稀土掘金

著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

更多Android技術分享可以關注@我,也可以加入QQ群號:Android進階學習群:345659112,一起學習交流。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2794433/,如需轉載,請註明出處,否則將追究法律責任。

相關文章