glReadPixels для EGLImage прямая текстура медленнее, чем glReadPixels для 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 GPU) мой новый код значительно медленнее, чем раньше! Как это может быть?

В новом коде поток 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, мне приходилось менять реализацию для потока 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.EGLC продать к android.opop.EGLContext? То, что у меня есть сейчас, просто кажется уродливым и не кажется правильным, хотя этот предложенный кастинг тоже не подходит. Я что-то пропустил?

1 ответ

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

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

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

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

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

Другие вопросы по тегам