glReadPixels到EGLImage的直接纹理比glReadPixels要慢到ByteBuffer和glTexSubImage2D?

时间:2015-02-10 23:44:57

标签: android opengl-es opengl-es-2.0 egl

我有一个Android OpenGL-ES应用程序,有两个线程。调用线程1“显示线程”,其“混合”其当前纹理与从线程2发出的纹理a.k.a“工作线程”。线程2执行离屏渲染(渲染到纹理),然后线程1将此纹理与其自身纹理组合以生成显示给用户的帧。

我有一个有效的解决方案,但我知道这是低效的,我正在努力改进它。在它的OnSurfaceCreated()方法中,线程1创建了两个纹理。线程2,在它的draw方法中,将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()慢一个ByteBuffer,后面跟上面提到的ByteBuffer的glTexSubImage2D?我也对替代技术感兴趣,因为我不仅限于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(),它允许我将Thread 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的东西,我不得不改变Thread 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.EGLD显示为android.opengl.EGLDisplay,将javax.microedition.khronos.egl.EGLContext转换为android.opengl。 EGLContext?我现在所拥有的只是看起来很丑陋并且感觉不对,尽管这个提议也不合适。我错过了什么吗?

1 个答案:

答案 0 :(得分:2)

根据您尝试做的描述,这两种方法听起来比必要的方式更复杂,效率更低。

我一直都了解EGLImage的方式,它是在不同的进程之间共享图像的机制,也可能是不同的API。

对于相同过程中的多个OpenGL ES上下文,您可以简单地共享纹理。您需要做的就是使两个上下文都属于同一个共享组,并且它们都可以使用相同的纹理。在您的用例中,您可以使用FBO将一个线程渲染到纹理,然后在另一个线程中从中进行采样。这样,就没有额外的数据复制,您的代码应该变得更加简单。

唯一有点棘手的方面是同步。 ES 2.0在API中没有同步机制。您可以做的最好的事情是在一个线程中调用glFinish()(例如,在线程2完成渲染到纹理之后),然后使用标准IPC机制来发信号通知另一个线程。 ES 3.0具有同步对象,使其更加优雅。

我之前的答案草拟了创建位于同一共享组中的多个上下文所需的一些步骤:about opengles and texture on android。在同一个共享组中创建多个上下文的关键部分是eglCreateContext的第三个参数,您可以在其中指定要与之共享对象的上下文。