基于Qt WidgetsQt程序,控件的刷新默认情况下都是在UI线程中依次进行的,换言之,各个控件的QWidget::paintEvent方法会在UI线程中串行地被调用。如果某个控件的paintEvent非常耗时(等待数据时间+CPU处理时间+GPU渲染时间),会导致刷新帧率下降,界面的响应速度变慢。

假如这个paintEvent耗时的控件没有使用OpenGL渲染,完全使用CPU渲染。这种情况处理起来比较简单,只需要另外开一个线程用CPU往QImage里面渲染,当主线程调用到这个控件的paintEvent时,再把渲染好的QImage画出来就可以了,单纯绘制一个QImage还是很快的。

如果这个paintEvent耗时的控件使用了OpenGL渲染,情况会复杂一些,因为想要把OpenGL渲染过程搬到另外一个线程中并不是直接把OpenGL调用从UI线程搬到渲染线程就可以的,是需要做一些准备工作的。另外,UI线程如何使用渲染线程的渲染结果也是一个需要思考的问题。


QT opengl多线程实现原理

QT里的qopenglwidget提供了对多线程的知识,根据文档所说,想要在另一个线程中执行渲染操作,需要将该widget的context通过movetothread到该线程,手动makecurrent和donecurrent,然后执行渲染操作。总而言之QT中想让opengl在另一个线程渲染,需要的东西只有一个:属于该线程的context。知道这个,就可以以很多方法实现该功能了。

方法1

qt的example里面有一个threadedopengl例子,里面就是通过aboutToCompose和frameSwapped两个信号来控制渲染的,重写paintevent为空函数。当aboutTocompose时,停止渲染,可以以获取该线程互斥量的形式完成,交换完成后,释放互斥量,并发送信号,指示渲染线程开始渲染。渲染线程首先请求context,通过发送信号给gui线程,并用条件变量等待,gui将context所有权交给渲染线程,唤醒条件变量,渲染线程执行渲染操作,再重新转移context所有权,并用qmetaobject向其发送一个update操作。update操作又会触发abouttocompose,这样就不停渲染了。

方法2

虽然上面的方法很好,但是在同一级父窗口中有多个widget的时候就会出问题,原因是update会导致两个widget的abouttocompose,QT在发送这个信号之前会makecurrent,但如果有一个widget正在渲染,context的所有权并不属于gui时就会报错。所以需要用另一种方法,这里用渲染到纹理的方法解决了。QT中有QOpenGLContext和QOffscreenSurface,这两个的组合可以将渲染结果渲染到纹理上,这样gui显示该纹理,就能达到同样的效果。使用方法查看QT文档,这里就不说明了。