在QWidget中,可以使用QOpenglWidget调用opengl接口进行渲染,因为QWidget大部分控件都是依赖于平台的(cpu绘制),所以我们调用opengl的接口时不需要考虑opengl的context共享,所以相对简单。但是qml的渲染策略有所不同,在可以使用硬件加速的环境下,qml是把当前所有可视元素都“堆放”在一起,然后统一使用gpu渲染。所以如果我们想要在qml中使用opengl接口进行绘制,需要考虑调用的时机以及不同窗口之间资源的共享,下面提供一种方法。
想要在qml中使用,我们需要继承QQuickItem,现在我们定义一个MyCube 用来绘制一个立方体。
class CubeRender;
class MyCube : public QQuickItem
{
Q_OBJECT
public:
explicit MyCube(QQuickItem* parent = nullptr);
protected slots:
void sync();
void cleanUp();
private:
CubeRender* m_render{nullptr};
signals:
};
其中CubeRender 是对opengl接口的一个封装,稍后再解释。先看看quick的渲染流程:
这张图截取自官方文档,从中可以很清楚的看到,GUI线程调用updatePolish之后,会把qml的视图元素传入渲染线程,在数据传递过程中,GUI线程会被lock,当传递完毕,渲染线程会发射beforeRendering信号,解锁GUI线程,然后GUI线程去做别的工作,渲染线程则进行渲染工作。
一般opengl不支持跨线程调用,所以我们想创建opengl资源和使用相关的api都必须在渲染线程中用同步的方式调用。
所以,我们需要在渲染线程发出beforeSynchronizing的信号时创建我们的opengl资源,在渲染线程发出beforeRendering的时候调用绘制接口,需要注意的是,由于渲染线程和gui线程并非一个线程,所以在连接信号槽的时候,我们需要使用Qt::DirectConnection来强制同步。
下面我们来看一下中CubeRender:
class CubeRender: protected QOpenGLFunctions_4_5_Core
{
public:
CubeRender();
void initializeGL();
void resizeGl(int w, int h);
void render();
protected:
QOpenGLShaderProgram m_shaderProgram;
};
我这边是仿造QOpengWidget的形式对opengl接口进行了封装,主要有三个接口:
void initializeGL(); 用来初始化opengl资源,包括创建VBO,VAO以及链接shader。
void resizeGl(int w, int h);当窗口大小发生改变的时候调用,用来修改视口的大小以及重新生成相关的视图矩阵。
void render(); 进行绘制。值得注意的是,由于qml大部分控件都是调用opengl绘制的,而且opengl本质是一个状态机,所以在绘制前需要对VBO这些对象重新进行bind。
(可以参考opengl相关教程来理解这些操作。)
下面就比较简单了,我们在收到beforeSynchronizing信号的时候初始化opengl资源。
connect(this,&QQuickItem::windowChanged,this,[this](QQuickWindow* window){
if(window)
{
connect(window,&QQuickWindow::beforeSynchronizing,this,&MyCube::sync,
Qt::DirectConnection);
}
},Qt::DirectConnection);
void MyCube::sync()
{
if(!m_render)
{
m_render = new CubeRender;
m_render->initializeGL();
m_render->resizeGl(width(),height());
}
在收到beforeRendering的时候进行绘制:
connect(window(), &QQuickWindow::beforeRendering, this, [this]()
{
m_render->render();
}, Qt::DirectConnection);
当窗口大小变化的重新调整视口大小:
connect(this, &QQuickItem::widthChanged, this, [this]()
{
m_render->resizeGl(this->width(), this->height());
});
connect(this, &QQuickItem::heightChanged, this, [this]()
{
m_render->resizeGl(this->width(), this->height());
});
接下来把MyCube注册成QML元素就可以使用了。
qmlRegisterType("cube.utility", 1, 0, "MyCube");
此外,还有一种方法是继承QQuickFramebufferObject,来进行opengl渲染。