多个QGLWidget同时不适用于视频用途

时间:2018-04-23 08:21:19

标签: qt opengl qglwidget

如果我想使用QGLWidget显示视频,则会出现问题。有一个实例可以工作,但它没有,小部件是黑色的,多次出现(例如在QGridLayout中使用)。

我将QGLWidget子类化为播放视频的方式(通过使用我的QPixmap方法刷新我要展示的setCurrentImage):

class GLWidget : public QGLWidget
{
    Q_OBJECT

public:
    GLWidget(QGLWidget* shareWidget = 0, QWidget* parent = 0)
        : QGLWidget(parent, shareWidget)
    {
        //...
        // I do this because the QPixmap to refresh is produced in a different thread than UI thread.
        connect(this, SIGNAL(updated()), this SLOT(update()));
    }

    //...

    void setCurrentImage(const QPixmap& pixmap)
    {
        m_mutex.lock();
        m_pixmap = pixmap;
        m_mutex.unlock();

        emit updated();
    }

protected:
    void initializeGL()
    {
        static const int coords[4][2] = {{ +1, -1 }, { -1, -1 }, { -1, +1 }, { +1, +1 }};
        for(int j = 0; j < 4; ++j){
            m_texCoords.append(QVector2D(j == 0 || j == 3, j == 0 || j == 1));
            m_vertices.append(QVector2D(0.5 * coords[j][0], 0.5 * coords[j][1]));
        }

        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
        glEnable(GL_TEXTURE_2D);

#define PROGRAM_VERTEX_ATTRIBUTE 0
#define PROGRAM_TEXCOORD_ATTRIBUTE 1

        QGLShader* vShader = new QGLShader(QGLShader::Vertex, this);
        const char* szVShaderCode = /*...*/;
        vShader->compileSourceCode(szVShaderCode);

        QGLShader *fShader = new QGLShader(QGLShader::Fragment, this);
        const char* szFShaderCode = /*...*/;
        fShader->compileSourceCode(szFShaderCode);

        m_pProgram = new QGLShaderProgram(this);
        m_pProgram->addShader(vShader);
        m_pProgram->addShader(fShader);
        m_pProgram->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE);
        m_pProgram->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);
        m_pProgram->link();

        m_pProgram->bind();
        m_pProgram->setUniformValue("texture", 0);
    }

    void paintGL()
    {
        qglClearColor(Qt::black);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // I use some uniform values to "modify" image
        m_pProgram->setUniformValue(/*...*/);
        //...

        QMatrix4x4 m;
        m.ortho(-0.5f, +0.5f, +0.5f, -0.5f, 4.0f, 15.0f);
        m.translate(0.0f, 0.0f, -10.0f);

        m_pProgram->setUniformValue("matrix", m);
        m_pProgram->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
        m_pProgram->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
        m_pProgram->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, m_vertices.constData());
        m_pProgram->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, m_texCoords.constData());

        glBindTexture(GL_TEXTURE_2D, m_texture);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);        
    }

    void resizeGL(int iWidth, int iHeight)
    {
        glViewport(0, 0, iWidth, iHeight);
    }

private slots:
    void update()
    {
        QPixmap pixmap;

        m_mutex.lock();
        pixmap = m_pixmap;
        m_mutex.unlock();

        m_texture = bindTexture(pixmap, GL_TEXTURE_2D);
        updateGL();
    }

private:
    QMutex m_mutex;
    QPixmap m_pixmap;

    QVector<QVector2D> m_vertices;
    QVector<QVector2D> m_texCoords;
    QGLShaderProgram* m_pProgram;
};

然后,为了模拟多线程渲染(每个线程产生自己的视频图像),我写了这些小类。

图像制作者类:

class ImageProducer : public QThread
{
    Q_OBJECT

public:
    ImageProducer(QGLWidget* pGLWidget)
        : QThread(pGLWidget), m_pGLWidget(pGLWidget)
    {
        m_pixmap = QPixmap("fileName.jpg");
        m_bMustStop = false;
    }

protected:
    void run()
    {
        while(!m_bMustStop){
            static_cast<GLWidget*>(m_pGLWidget)->setCurrentImage(m_pixmap);

            // Simulate a frame rate
            msleep(1000 / /*FRAME_RATE*/);
        }
    }

private:
    QGLWidget* m_pGLWidget;
    QPixmap m_pixmap;
    bool m_bMustStop;
};

渲染类:

#define ROWS 1
#define COLS 1

class Window : public QWidget
{
public:
    Window()
    {
        QGridLayout* pMainLayout = new QGridLayout(this);
        setLayout(pMainLayout);

        for(int i = 0; i < ROWS; ++i){
            for(int j = 0; j < COLS; ++j){
                QGLWidget* pGLWidget = new GLWidget();
                pMainLayout->addWidget(pGLWidget, i, j);

                ImageProducer* pImageProducer = new ImageProducer(pGLWidget);
                pImageProducer->start();
            }
        }
    }
};

停止使用代码示例^^问题是 ROWS = 1 COLS = 1 (请参阅Window类)它有效,但我有黑色具有其他价值观的小部件......我迷路了,我错过了什么?

谢谢!

编辑:(上下文总是多GLWidget实例,只有一个可以正常工作)

我发现奇怪的事情:我覆盖QGLWidget mouseMoveEvent(因此,在我的GLWidget课程中)只调用updateGL();。事实上,当我按下并移动鼠标时,当前代码没有任何反应。但如果我替换(在ImageProducer run()方法中):

static_cast<GLWidget*>(m_pGLWidget)->setCurrentImage(m_pixmap);

通过

static_cast<GLWidget*>(m_pGLWidget)->setCurrentImage(QPixmap("fileName.jpg"));

只要我移动鼠标,图像就会在当前组件中刷新。当我将其释放或在其他组件中进行时,背景会再次变黑。

1 个答案:

答案 0 :(得分:1)

我在与OP聊天后写下了答案,为了别人的利益。以下是导致解决问题的主要主题。

注意:在撰写本文时,Qt OpenGL模块已弃用,不建议使用它。根据官方documentation,建议的方法是在GUI模块中使用OpenGL *类。

纹理

上面代码的主要问题主要是由于纹理的处理方式。

首先, bindTexture()方法在OpenGL术语中不绑定,但实际上创建 OpenGL纹理并上传数据作为参数传递给它( QImage QPixmap )。这令人困惑,可能导致严重的问题。在OP发布的代码中,他基本上泄漏内存在每次帧更新时分配新纹理。

为确保不泄漏内存,至少应释放先前分配的纹理,并调用QGLWidget::deleteTexture

但是,值得注意的是,有更好的方法可以避免不必要的内存碎片效率低下。在这种情况下,最好的方法是分配纹理一次,并在必要时简单地更新其内容。这可能无法直接使用旧Qt OpenGL模块提供的API,但您可以始终混合使用Qt和本机 OpenGL代码。

上下文处理

另一个问题是处理OpenGL上下文的方式。根据经验,在向OpenGL实现发出命令之前,应始终确保正确的上下文当前。有时,Qt会自动为您执行此操作(即在调用 paintGL()方法之前)。

在这种情况下,我们需要在调用bindTexture方法之前显式地使QGLWidget的上下文成为当前上下文,否则它将影响当前生成的 last 上下文。这就是为什么只有最后一个小部件显示某些内容,直到somewhone通过与其他小部件交互来触发makeCurrent调用。

线程

这里有几个问题。首先,在GUI线程以外的线程中使用 QPixmap 对象是不安全的。 QPixmap旨在优化屏幕上的像素图blitting。在这种情况下,pixmap实际上只是要上传到OpenGL实现的帧,它处理所有渲染。因此,使用QImage是安全的。

另一个问题是 GLWidget :: setCurrentImage(),因此 bindTexture 方法直接从 run() ImageProducer 线程的方法。这可能是因为我们需要使窗口小部件上下文成为当前,但是不能从GUI线程以外的线程调用 makeCurrent()(更多细节here )。

解决此问题的一种可能方法是向ImageProducer添加信号以通知帧已更新,并将此信号连接到 setCurrentImage()插槽。 Qt的信号槽机制将确保在GLWidget线程中执行setCurrentImage,即GUI线程。

代码

以下是对上面发布的代码的建议(和测试)修改。

ImageProduer类

添加信号:

 void imageUpdated(const QImage &image);

并在更新帧时发出它:

void ImageProducer::run()
{
    while(!m_bMustStop){
        QImage frame(m_pixmap.width(), m_pixmap.height(), m_pixmap.format());
        QPainter painter(&frame);
        painter.drawImage(QPoint(), m_pixmap);
        painter.setFont(QFont("sans-serif", 22));
        painter.setPen(Qt::white);
        painter.drawText(20, 50, QString("Frame: %1").arg(QString::number(_frameCount)));
        painter.end();

        emit imageUpdated(frame);

        msleep(1000 / FRAME_RATE);

        _frameCount++;
    }
}

GLWidget类

确保setCurrentImage方法处理OpenGL上下文并销毁旧纹理:

void GLWidget::setCurrentImage(const QImage& pixmap)
{
    m_mutex.lock();
    m_pixmap = pixmap;
    m_mutex.unlock();

    makeCurrent();

    deleteTexture(m_texture);
    m_texture = bindTexture(pixmap, GL_TEXTURE_2D);

    doneCurrent();

    emit updated();
}