如果我想使用QGLWidget
显示视频,我遇到了问题。对于一个实例,它可以工作,但它不起作用,并且小部件是黑色的,多次出现(例如QGridLayout
用法)。
我QGLWidget
这种方式子类来播放视频(通过使用setCurrentImage
方法刷新我想显示的QPixmap
):
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"));
只要我移动鼠标,图像就会在当前组件中刷新。当我释放它,或者在其他组件中执行此操作时,背景再次变黑。
为了他人的利益,我在与 OP 聊天后写下答案。以下是导致问题解决的主要主题。
注意:在撰写本文时,Qt OpenGL模块已被弃用,不鼓励使用。根据官方文档,建议的方法是在 GUI 模块中使用 OpenGL* 类。
纹理
上面代码的主要问题主要是由于处理纹理的方式。
首先,bindTexture()方法不是在 OpenGL 术语中绑定,而是实际创建OpenGL 纹理并上传作为参数传递给它的数据(QImage或QPixmap)。这令人困惑,并可能导致严重的问题。在OP发布的代码中,他本质上是在泄漏内存,在每次帧更新时分配一个新的纹理。
为了确保您不会泄漏内存,您至少应该释放先前分配的纹理,调用 QGLWidget::d eleteTexture。
但是,值得注意的是,有更好的方法来避免不必要的内存碎片和低效率。在这种情况下,最好的方法是分配纹理一次,然后在必要时简单地更新其内容。这可能无法直接使用旧Qt OpenGL模块提供的API实现,但您可以随时混合使用Qt和本机OpenGL代码。
上下文处理
另一个问题是处理OpenGL上下文的方式。根据经验,在向 OpenGL 实现发出命令之前,应始终确保正确的上下文是最新的。有时,Qt会自动为你执行此操作(即在调用paintGL()方法之前)。
在这种情况下,我们需要在调用 bindTexture 方法之前显式地将 QGLWidget 的上下文设置为当前上下文,否则它将影响最后一个当前上下文。这就是为什么只有最后一个小部件显示一些东西,直到有人通过与其他小部件交互触发makeCurrent调用。
线程
这里有几个问题。首先,在 GUI 线程以外的线程中使用QPixmap对象是不安全的。QPixmap旨在优化屏幕上的像素图块传输。在这种情况下,pixmap实际上只是要上传到OpenGL实现的帧,它处理所有渲染。因此,改用QI法师是安全的。
另一个问题是GLWidget::setCurrentImage()和bindTexture方法直接从ImageProducer线程的run()方法调用。这不可能是因为我们需要使小部件上下文成为当前上下文,但不可能从 GUI 线程以外的线程调用makeCurrent()(更多细节在这里)。
解决此问题的一种可能方法是向 ImageProducer 添加一个信号以通知帧已更新,并将此信号连接到setCurrentImage()插槽。Qt的信号槽机制将确保setCurrentImage在GLWidget线程(即GUI线程)中执行。
法典
以下是对上面发布的代码的建议(和测试)修改。
图像生产类
添加信号:
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();
}