如何将PBO与Qt OpenGL结合使用

How to use PBO with Qt OpenGL

本文关键字:OpenGL 结合 Qt PBO      更新时间:2023-10-16

我正在尝试使用QOpenGLBuffer作为像素缓冲区对象。目标是显示高分辨率的视频流(4K,60FPS),所以我需要良好的性能。

由于我刚开始使用OpenGL,我首先尝试以最好的方式显示一个简单的2D纹理。我已经包含了VBO和VAO,下一步(正如我所读的)将是使用PBO来获得更好的性能。

有PBO的教程,但有glGenBufferARB(),而不是QOpenGLBuffer。

这是我的代码:

glwidget.h

#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QDebug>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>


class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit GLWidget(QWidget *parent = 0);
    ~GLWidget();
    void initializeGL();
    void paintGL();
    void resizeGL(int w, int h);
    void LoadGLTextures();
private :
    QOpenGLShaderProgram *program;
    QOpenGLBuffer vbo;
    QOpenGLVertexArrayObject vao;
    GLuint tex;
    GLint vertexLocation;
    GLint texcoordLocation;
    int tailleVerticesBytes;
    int tailleCoordTexturesBytes;
    float vertices[8];
    float coordTexture[8];

public slots:

private slots:

};
#endif // GLWIDGET_H

glwidget.cpp

#ifndef BUFFER_OFFSET
#define BUFFER_OFFSET(offset) ((char*)NULL + (offset))
#include "glwidget.h"
#include <QElapsedTimer>

GLWidget::GLWidget(QWidget *parent) :
        QOpenGLWidget(parent)
{
    tailleVerticesBytes = 8*sizeof(float);
    tailleCoordTexturesBytes = 8*sizeof(float);
}
GLWidget::~GLWidget(){
    vao.destroy();
    vbo.destroy();
    delete program;
    glDeleteTextures(1, &tex);
}
void GLWidget::LoadGLTextures(){
    QImage img;
    if(!img.load("C:\Users\Adrien\Desktop\open3.bmp")){
        qDebug()<<"Image loading failed";
    }
    QImage t = (img.convertToFormat(QImage::Format_RGBA8888)).mirrored();
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, t.width(), t.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, t.bits());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture( GL_TEXTURE_2D, 0);

}
void GLWidget::initializeGL(){

    float verticesTmp[] = {-1.0,-1.0,  1.0,-1.0,  1.0,1.0,  -1.0,1.0};
    float coordTextureTmp[] = {0.0,0.0,  1.0,0.0,  1.0,1.0,  0.0,1.0};
    for(int i = 0; i<8; i++){
        vertices[i] = verticesTmp[i];
        coordTexture[i] = coordTextureTmp[i];
    }
    initializeOpenGLFunctions();
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
    LoadGLTextures();
    //Shader setup
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    const char *vsrc =
        "#version 150 coren"
        "in vec2 in_Vertex;n"
        "in vec2 vertTexCoord;n"
        "out vec2 fragTexCoord;n"
        "void main(void)n"
        "{n"
        "    gl_Position = vec4(in_Vertex, 0.0, 1.0);n"
        "    fragTexCoord = vertTexCoord;n"
        "}n";
    vshader->compileSourceCode(vsrc);
    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    const char *fsrc =
            "#version 150 coren"
            "uniform sampler2D tex;n"
            "in vec2 fragTexCoord;n"
            "void main(void)n"
            "{n"
            "    gl_FragColor = texture2D(tex,fragTexCoord);n"
            "}n";
    fshader->compileSourceCode(fsrc);
    program = new QOpenGLShaderProgram;
    program->addShader(vshader);
    program->addShader(fshader);
    program->link();
    program->bind();
    glActiveTexture(GL_TEXTURE0);
    program->setUniformValue("tex", 0);
    vertexLocation = glGetAttribLocation(program->programId(), "in_Vertex");
    texcoordLocation = glGetAttribLocation(program->programId(), "vertTexCoord");
    //VAO setup
    vao.create();
    vao.bind();
    //VBO setup
    vbo.create();
    vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
    vbo.bind();
    vbo.allocate(tailleVerticesBytes + tailleCoordTexturesBytes);
    vbo.write(0, vertices, tailleVerticesBytes);
    vbo.write(tailleVerticesBytes, coordTexture, tailleCoordTexturesBytes);
    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 2);
    program->enableAttributeArray(texcoordLocation);
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, tailleVerticesBytes, 2);
    vbo.release();
    vao.release();
    program->release();

}
void GLWidget::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT);
    program->bind();
    {
        vao.bind();
            glBindTexture(GL_TEXTURE_2D, tex);
                glDrawArrays(GL_QUADS, 0, 4);
            glBindTexture(GL_TEXTURE_2D, 0);
        vao.release();
    }
    program->release();
}
void GLWidget::resizeGL(int w, int h){
    glViewport(0, 0, (GLint)w, (GLint)h);
}

#endif

那么,基本上,我该如何在这个代码中使用PBO呢?

首先要做的是在指定类型(QOpenGLBuffer::PixelUnpackBuffer)的同时创建一个QOpenGLBuffer对象,然后我想我需要上传缓冲区上的像素,并最终使用它而不是glTexImage2D?这只是一个全球性的想法,我不知道该怎么做

谢谢。

目标是显示高分辨率的视频流(4K,60FPS),所以我需要良好的性能。

唯一正确的方法是使用一些加速演示API(与OpenGL无关)。

如果你想坚持使用OpenGL,你至少需要让GPU进行视频解码并上传到纹理中。如何做到这一点取决于您的操作系统和GPU。例如,在Linux下,使用NVIDIA,可以使用VDPAU进行加速解码,使用NV_VDPAU_interop获得由解码帧填充的纹理。

如果你仍然想使用PixelUnpack缓冲区对象(PUBO;你正在上传到GL=>这是一个解包),那么几乎没有什么魔力。创建一个:

QOpenGLBuffer *pubo = new QOpenGLBuffer(QOpenGLBuffer::PixelUnpackBuffer);
pubo->create();

然后用你的帧数据填充它:

pubo->bind();
pubo->allocate(...);  // or, if already allocated, also write/map

现在,PUBO的效果是,如果绑定了某个调用,则某些调用将更改语义,从而不是从用户内存而是从PUBO读取数据。值得注意的是,上传纹理数据的调用。因此,如果你有你的纹理(并且你应该使用QOpenGLTexture,它使用不可变的存储,而不是手动调用glTexImage),你可以这样做:

pubo->bind();
glBindTexture(GL_TEXTURE_2D, textureId);
glTexImage2D(GL_TEXTURE_2D, 
             level, 
             internalFormat,
             width,
             heigth,
             border,
             externalFormat,
             type,
             data);

由于存在PUBO绑定,最后一个参数(data)更改了语义:它不再是指向客户端内存的指针,而是指向当前绑定的像素解包缓冲区对象的字节偏移量。因此,如果纹理数据从偏移量0开始进入缓冲区,则需要在那里传递0(或者实际上传递(const GLvoid *)0)。否则,您需要相应地进行调整。

现在您可以发布pubo:

pubo->release();

然后像往常一样在着色器中使用纹理,一切都会很好。


除了如果你直接使用纹理,你不会得到任何性能提升!这种复杂设置的全部目的是允许GL异步传输图像数据,同时渲染前一帧中上传的数据。如果您立即使用图像数据,GL需要同步整个管道,以便等待图像数据上传到GPU。

因此,该场景中的典型设置是以循环方式使用多个PUBO。例如,如果您有两个(在ping Pong中),则在一个PBO中上载数据,并使用前一个PBO填充和绘制纹理。这应该为GL在总线上实际传输当前数据争取"足够的时间",因此在下一帧,纹理上传和绘制将立即找到可用的数据。

理想情况下,您还可以使用共享的OpenGL上下文从另一个线程执行PUBO中的数据上传,并在上传完成时使用栅栏向渲染线程发出信号,以便填充纹理。您还可以使用变形、固定映射、非同步写入等等来进一步构建。

OpenGL Insights的第28/29章提供了对所有这些的深入解释,我在这里无法完整复制,并附带了一些代码。

您也可以在OpenGLwiki上找到更多关于缓冲区对象流的信息。