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

问题描述:

我有一个具有两个线程的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?我现在所拥有的东西看起来很丑陋,并且感觉不对,虽然这个提议的演员也不坐正确。我错过了什么吗?

根据你要做什么的描述,这两种方法听起来都比所需的方式更加复杂和低效。

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

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

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

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