2015-02-10 458 views
1

我有一個具有兩個線程的Android OpenGL-ES應用程序。將線程1稱爲「顯示線程」,其將其當前紋理與從線程2 a.k.a發出的紋理「工作線程」「混合」。線程2執行離屏渲染(渲染爲紋理),然後線程1將此紋理與其自己的紋理結合起來以生成顯示給用戶的幀。glReadPixels到EGLImage直接紋理比glReadPixels更慢比ByteBuffer和glTexSubImage2D?

我有一個工作解決方案,但我知道它效率低下,我正試圖改進它。在它的OnSurfaceCreated()方法中,線程1創建兩個紋理。線程2在它的繪製方法中,將glReadPixels()轉換爲ByteBuffer(讓我們將其稱爲bb)。線程2然後向線程1發信號通知一個新的幀已準備就緒,線程1調用glTexSubImage2D(bb)以使用來自線程2的新數據更新其紋理,並繼續進行「混合」以生成新的框架。

這種體系結構在某些Android設備上的效果比其他設備更好,我可以通過使用PBO獲得略微的性能提升。但我想通過使用所謂的「直接紋理」通過EGL圖像擴展(https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis),我可以通過消除昂貴的glTexSubImage2D()調用來獲得一些好處。是的,我仍然有glReadPixels()調用仍然困擾我,但至少我應該測量一些改進。事實上,至少在三星Galaxy Tab S(Mali T628 GPU)上我的新代碼顯着比以前慢!怎麼會這樣?

在新的代碼線程1使用gralloc實例化EGLImage對象,並進行到其綁定到質地:

// note gbuffer::create() is a wrapper around gralloc 
 
buffer = gbuffer::create(width, height, gbuffer::FORMAT_RGBA_8888); 
 
EGLClientBuffer anb = buffer->getNativeBuffer(); 
 
EGLImageKHR pEGLImage = _eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer)anb, attrs); 
 
glBindTexture(GL_TEXTURE_2D, texid); // texid from glGenTextures(...) 
 
_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, pEGLImage);

那麼線程2,在它的主循環做它的摘屏幕渲染到紋理的東西,並通過glReadPixels()將數據重新推回到線程1,目標地址作爲EGLImage後面的後備存儲:

void* vaddr = buffer->lock(); 
 
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, vaddr); 
 
buffer->unlock();

這如何可以比glReadPixels()成字節緩衝區隨後glTexSubImage2D從上述ByteBuffer的慢?我也對替代技術感興趣,因爲我不僅限於OpenGL-ES 2.0,而且可以使用OpenGL-ES 3.0。我嘗試過FBO,但遇到了一些問題。

作爲對第一個答案的迴應,我決定刺探實施不同的方法。也就是說,共享線程1和線程2之間的紋理。雖然我沒有共享部分工作,但我確實有線程1的EGLContext傳遞給線程2的EGLContext,因此理論上線程2可以與線程共享紋理1.通過這些更改,並保留glReadPixels()和glTexSubImage2D()調用,該應用程序可以正常工作,但速度比以前慢得多。奇怪。

我發現的另一個奇怪之處是處理javax.microedition.khronos.egl.EGLContext和android.opengl.EGLContext之間的區別。GLSurfaceView公開接口方法setEGLContextFactory(),讓我通過線程1的EGLContext主題2,如下面的:

public Thread1SurfaceView extends GLSurfaceView { 
 
    public Thread1SurfaceView(Context context) { 
 
    super(context); 
 
    // here is how I pass Thread 1's EGLContext to Thread 2 
 
    setEGLContextFactory(new EGLContextFactory() { 
 
     @Override 
 
     public javax.microedition.khronos.egl.EGLContext createContext(
 
     final javax.microedition.khronos.egl.EGL10 egl, 
 
     final javax.microedition.khronos.egl.EGLDisplay display, 
 
     final javax.microedition.khronos.egl.EGLConfig eglConfig) { 
 
      // Configure context for OpenGL ES 3.0. 
 
      int[] attrib_list = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE}; 
 
      javax.microedition.khronos.egl.EGLContext renderContext = 
 
      egl.eglCreateContextdisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); 
 
      mThread2 = new Thread2(renderContext); 
 
     } 
 
    }); 
 
}

以前,我使用的東西出EGL14命名空間,但由於GLSurfaceView的接口顯然依賴於EGL10的東西,我必須改變線程2的實現。到處使用EGL14我用javax.microedition.khronos.egl.EGL10取代。然後我的着色器停止編譯,直到我將GLES3添加到屬性列表。現在,雖然比以前慢了(但接下來我將刪除對glReadPixels和glTexSubImage2D的調用)。

我的後續問題是,這是處理javax.microedition.khronos.egl。*與android.opengl。*問題的正確方法嗎?我可以將javax.microedition.khronos.egl.EGL10轉換爲android.opengl.EGL14,將javax.microedition.khronos.egl.EGLDisplay轉換爲android.opengl.EGLDisplay,並將javax.microedition.khronos.egl.EGLContext轉換爲android.opengl。 EGLContext?我現在所擁有的東西看起來很醜陋,並且感覺不對,雖然這個提議的演員也不坐正確。我錯過了什麼嗎?

回答

2

根據你要做什麼的描述,這兩種方法聽起來都比所需的方式更加複雜和低效。

我一直了解EGLImage的方式,它是一種在不同的進程之間共享圖像的機制,以及可能不同的API。

對於相同過程中的多個OpenGL ES上下文,您可以簡單地共享紋理。所有你需要做的就是使兩個上下文成爲同一個共享組的一部分,並且它們都可以使用相同的紋理。在您的用例中,您可以使用FBO將一個線程渲染爲紋理,然後在另一個線程中對其進行採樣。這樣,就沒有額外的數據複製,並且你的代碼應該變得更簡單。

唯一稍微棘手的方面是同步。 ES 2.0在API中沒有同步機制。你可以做的最好的方法是在一個線程中調用glFinish()(例如,在線程2完成渲染之後),然後使用標準的IPC機制來發信號通知另一個線程。 ES 3.0具有同步對象,這使得它更加優雅。

我早期的答案在這裏草繪了一些創建同一共享組中多個上下文所需的步驟:about opengles and texture on android。在同一個共享組中創建多個上下文的關鍵部分是eglCreateContext的第三個參數,您可以在其中指定要與之共享對象的上下文。