如果我想使用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"));
只要我移动鼠标,图像就会在当前组件中刷新。当我将其释放或在其他组件中进行时,背景会再次变黑。
答案 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();
}