glReadPixels to EGLImage направляет текстуру медленнее, чем glReadPixels to ByteBuffer и glTexSubImage2D?

У меня есть приложение Android OpenGL-ES с двумя потоками. Назовите поток 1 «потоком отображения», который «смешивает» свою текущую текстуру с текстурой, исходящей из потока 2, также известной как «рабочий поток». Поток 2 выполняет рендеринг вне экрана (рендеринг в текстуру), а затем поток 1 объединяет эту текстуру со своей собственной текстурой для создания кадра, который отображается пользователю.

У меня есть рабочее решение, но я знаю, что оно неэффективно, и пытаюсь его улучшить. В методе OnSurfaceCreated() поток 1 создает две текстуры. Поток 2 в своем методе отрисовки выполняет вызов glReadPixels() в ByteBuffer (назовем его bb). Затем поток 2 сообщает потоку 1, что новый кадр готов, и в этот момент поток 1 вызывает glTexSubImage2D(bb), чтобы обновить свою текстуру новыми данными из потока 2 и продолжить ее «смешивание». для создания нового кадра.

Эта архитектура работает лучше на некоторых устройствах Android, чем на других, и мне удалось добиться небольшого повышения производительности с помощью PBO. Но я понял, что с помощью так называемых «прямых текстур» через расширение EGL Image (https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis) я бы получил некоторое преимущество, избавившись от дорогостоящего вызова glTexSubImage2D(). Да, у меня по-прежнему будет вызов glReadPixels(), который меня все еще беспокоит, но, по крайней мере, я должен измерить некоторое улучшение. На самом деле, по крайней мере, на Samsung Galaxy Tab S (графический процессор Mali T628) мой новый код значительно медленнее, чем раньше! Как это может быть?

В новом коде поток 1 создает объект EGLImage с помощью gralloc и привязывает его к текстуре:

// 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 в своем основном цикле выполняет внеэкранный рендеринг в текстуру и, по сути, отправляет данные обратно в поток 1 через glReadPixels() с адресом назначения в качестве резервного хранилища позади EGLImage:

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

Как это может быть медленнее, чем glReadPixels() в ByteBuffer, за которым следует glTexSubImage2D из вышеупомянутого ByteBuffer? Меня также интересуют альтернативные методы, поскольку я не ограничен OpenGL-ES 2.0 и могу использовать OpenGL-ES 3.0. Я пробовал FBO, но столкнулся с некоторыми проблемами.

В ответ на первый ответ я решил попробовать реализовать другой подход. А именно, совместное использование текстур между потоком 1 и потоком 2. Хотя у меня еще не работает разделяющая часть, у меня есть EGLContext потока 1, переданный в EGLContext потока 2, так что теоретически поток 2 может обмениваться текстурами с потоком 1. С этими изменениями и оставшимися вызовами glReadPixels() и glTexSubImage2D() приложение работает, но намного медленнее, чем раньше. Странный.

Другая странность, которую я обнаружил, связана с разницей между javax.microedition.khronos.egl.EGLContext и android.opengl.EGLContext. GLSurfaceView предоставляет интерфейсный метод setEGLContextFactory(), который позволяет мне передать EGLContext потока 1 в поток 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.EGLDisplay в android.opengl.EGLDisplay и javax.microedition.khronos.egl.EGLContext в android.opengl. EGLКонтекст? То, что у меня есть прямо сейчас, просто кажется уродливым и неправильным, хотя этот предложенный кастинг тоже не подходит. Я что-то упускаю?


person Shehrzad Qureshi    schedule 10.02.2015    source источник


Ответы (1)


Основываясь на описании того, что вы пытаетесь сделать, оба подхода кажутся более сложными и неэффективными, чем необходимо.

Я всегда понимал EGLImage как механизм обмена изображениями между различными процессами и, возможно, разными API.

Для нескольких контекстов OpenGL ES в одном процессе можно просто поделиться текстурами. Все, что вам нужно сделать, это сделать оба контекста частью одной и той же общей группы, и они оба могут использовать одни и те же текстуры. В вашем случае использования вы можете иметь один поток, рендеринг текстуры с использованием FBO, а затем выборку из него в другом потоке. Таким образом, нет лишнего копирования данных, и ваш код должен стать намного проще.

Единственный немного сложный аспект — это синхронизация. ES 2.0 не имеет механизмов синхронизации в API. Лучшее, что вы можете сделать, это вызвать glFinish() в одном потоке (например, после того, как ваш поток 2 закончил рендеринг текстуры), а затем использовать стандартные механизмы IPC для передачи сигнала другому потоку. В ES 3.0 есть объекты синхронизации, которые делают его более элегантным.

В моем предыдущем ответе здесь описаны некоторые шаги, необходимые для создания нескольких контекстов, находящихся в одной группе общего доступа: об опенглах и текстурах на андроиде. Ключевой частью создания нескольких контекстов в одной группе общего доступа является третий аргумент eglCreateContext, где вы указываете контекст, с которым вы хотите поделиться объектами.

person Reto Koradi    schedule 11.02.2015