QOpenGLWidget оптимальное распределение текстурного буфера - PullRequest
0 голосов
/ 28 августа 2018

Возможно, мой вопрос связан не с самим Qt и / или QOpenGLWidget, а с буферами OpenGL в целом. В любом случае, я пытаюсь реализовать кроссплатформенный рендерер видеокадров YUV, который требует преобразования YUV в RGB и последующей визуализации результата на виджете.

Пока мне удалось следующее:

  1. Найдены два подходящих шейдера (1 фрагмент и 1 вершина) для улучшения преобразования YUV 2 RGB (пока наш проект поддерживает только Qt 5.6, лучшего способа для меня нет)
  2. Использовал QOpenGLWidget для получения корректно работающего виджета
  3. Сделал все возможное, чтобы QOpenGLTexture сделал рисунок

Здесь - мой очень схематичный код, который отображает видеокадры из необработанного файла YUV, и большая часть работы выполняется графическим процессором. Я был бы счастлив, если бы не проблема распределения буферов. Дело в том, что фреймы получены из некоего устаревшего кода, который предоставляет мне пользовательские обертки вокруг чего-то вроде unsigned char *data, поэтому я должен скопировать его так:

//-----------------------------------------
GLvoid*                 mBufYuv; // buffer somewhere
int                     mFrameSize; 
//-------------------------
void OpenGLDisplay::DisplayVideoFrame(unsigned char *data, int frameWidth, int frameHeight)
{
    impl->mVideoW = frameWidth;
    impl->mVideoH = frameHeight;
    memcpy(impl->mBufYuv, data, impl->mFrameSize);
    update();
}

При тестировании концепции размеры кадров и буферов были жестко закодированы, как:

// Called from the outside, assuming video frame height/width are constant
void OpenGLDisplay::InitDrawBuffer(unsigned bsize)
{
    impl->mFrameSize = bsize;
    impl->mBufYuv = new unsigned char[bsize];
} 

Классы текстур Qt хорошо послужили целям, так что ...

// Create y, u, v texture objects respectively    
impl->mTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
impl->mTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
impl->mTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
impl->mTextureY->create();
impl->mTextureU->create();
impl->mTextureV->create();

// Get the texture index value of the return y component
impl->id_y = impl->mTextureY->textureId();
// Get the texture index value of the returned u component
impl->id_u = impl->mTextureU->textureId();
// Get the texture index value of the returned v component
impl->id_v = impl->mTextureV->textureId();

А ведь сам рендеринг выглядит так:

void OpenGLDisplay::paintGL()
{
    // Load y data texture
    // Activate the texture unit GL_TEXTURE0
    glActiveTexture(GL_TEXTURE0);
    // Use the texture generated from y to generate texture
    glBindTexture(GL_TEXTURE_2D, impl->id_y);
    // Use the memory mBufYuv data to create a real y data texture
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW, impl->mVideoH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, impl->mBufYuv);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // Load u data texture
    glActiveTexture(GL_TEXTURE1);//Activate texture unit GL_TEXTURE1
    glBindTexture(GL_TEXTURE_2D, impl->id_u);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW/2, impl->mVideoH/2
                 , 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char*)impl->mBufYuv + impl->mVideoW * impl->mVideoH);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // Load v data texture
    glActiveTexture(GL_TEXTURE2);//Activate texture unit GL_TEXTURE2
    glBindTexture(GL_TEXTURE_2D, impl->id_v);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, impl->mVideoW / 2, impl->mVideoH / 2
                 , 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char*)impl->mBufYuv + impl->mVideoW * impl->mVideoH * 5/4);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // Specify y texture to use the new value can only use 0, 1, 2, etc. to represent
    // the index of the texture unit, this is the place where opengl is not humanized
    //0 corresponds to the texture unit GL_TEXTURE0 1 corresponds to the
    // texture unit GL_TEXTURE1 2 corresponds to the texture unit GL_TEXTURE2
    glUniform1i(impl->textureUniformY, 0);
    // Specify the u texture to use the new value
    glUniform1i(impl->textureUniformU, 1);
    // Specify v texture to use the new value
    glUniform1i(impl->textureUniformV, 2);
    // Use the vertex array way to draw graphics
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

Как я уже упоминал выше, он отлично работает, но это всего лишь демонстрационный скетч, цель состояла в том, чтобы реализовать универсальный рендеринг видео, что означает, что соотношение сторон, разрешение и размер кадра могут изменяться динамически; таким образом, буфер (GLvoid* mBufYuv; в приведенном выше коде) должен быть перераспределен, и, что еще хуже, мне придется записывать в него данные 25 раз в секунду. Выглядит определенно как что-то, что не будет работать слишком быстро с видео Full HD, например.

Конечно, возможны несколько тривиальных оптимизаций, приводящих к сокращению копирования данных, но Google сказал мне, что есть разные способы размещения буферов в OpenGL напрямую, по крайней мере, для PBO / PUBO и QOpenGLBuffer.

Теперь есть проблема - я совершенно запутался со всеми этими многими способами обработки текстур и не знаю ни лучшего / оптимального, ни того, который лучше всего подходит для моего случая.

Любой совет приветствуется.

...