将 Qt GUI 拆分为多个线程用于 GUI、模拟和 OpenGL 是不是可行?

Posted

技术标签:

【中文标题】将 Qt GUI 拆分为多个线程用于 GUI、模拟和 OpenGL 是不是可行?【英文标题】:Is it feasible to split Qt GUI into multiple threads for GUI, simulation, and OpenGL?将 Qt GUI 拆分为多个线程用于 GUI、模拟和 OpenGL 是否可行? 【发布时间】:2016-08-19 00:38:09 【问题描述】:

我正在尝试使用 Qt 为工作中的仪器模拟程序设计新布局。我们当前的 sim 在一个窗口中运行所有内容(我们同时使用了 glut(旧)和 fltk),它使用 glViewport(...)glScissor(...) 将仪器读数拆分为它们自己的视图,然后它使用某种形式的“ ortho2D" 调用以创建自己的虚拟像素空间。模拟器当前会更新仪器,然后在它们自己的视口中一一绘制,所有这些都在同一个线程中。

我们想找到一种更好的方法,因此我们选择了 Qt。我在一些重大限制下工作:

    每个仪表板仍需要位于其 OpenGL 视口中。有很多按钮和很多乐器。我的暂定解决方案是为每个使用 QOpenGLWidget。我在这方面取得了进展。 sim 不仅是一个漂亮的读数,而且还模拟了许多仪器作为仪器设计人员的反馈,因此它有时会产生大量的 CPU 负载。它不是一个完整的硬件模拟器,但它确实模拟了逻辑。我认为告诉仪器在其关联小部件的 paintEvent(...) 方法的开头进行自我更新是不可行的,因此我希望模拟更新在单独的线程中运行。 我们的客户可能有旧计算机,因此已排除更新版本的 OpenGL。我们仍在使用glBegin()glEnd() 以及介于两者之间的所有内容,并且这些工具会绘制大量可变符号,因此绘制需要很多时间,我想将绘制拆分到它自己的线程中。我还不知道 OpenGL 3 是否在桌面上,这对于渲染到屏幕外缓冲区是必要的(我认为)。

问题: QOpenGLWidget 没有可覆盖的“更新”方法,它只在小部件的paintEvent(...)paintGL(...) 调用期间进行绘制。

暂定方案:将模拟器拆分成三个线程:

    GUI:运行用户输入,paintEvent(...)paintGL(...)。 模拟器:运行所有仪器逻辑并更新符号系统的值。 绘图:将最新的符号系统渲染到屏幕外缓冲区(将使用帧缓冲区对象 (FBO))。

在这个设计中,跨线程对话是循环和单向的,GUI 线程提供输入,模拟器线程在其下一个循环中考虑该输入,绘图线程读取最新的符号系统并将其渲染到FBO 并将“下一帧可用”标志设置为 true(或者可能发出信号),然后 paintGL(...) 方法将采用该 FBO 并将其吐出到小部件,从而降低事件处理和 GUI 响应能力。继续这个循环。

底线问题:我读过here,GUI 操作不能在单独的线程中完成,那么我的方法是否可行?

如果可行,我们将不胜感激。

【问题讨论】:

通常所有绘图和小部件操作都需要在拥有“窗口”的线程中,这适用于普通的 GUI 小部件和窗口,以及 OpenGL。其他一切都可以在线程中。您可以修改正在绘制的对象,例如OpenGL 在另一个线程中,只是不在另一个线程上进行绘图。还要小心,不要修改刚刚绘制的对象。 为什么需要这样的设计? GUI 系统中的事件队列就是这样的队列。除非您处于专门的模式模式,否则您不会错过任何活动。这使得允许渲染阻塞输入处理是完全合理的,并且由于输入延迟的原因是常态。 因为事件处理在线程上是阻塞的,我不想将事件处理与可以持续很长时间的paintEvent(...) 捆绑在一起。我的印象是,如果事件系统被这样捆绑,那么 GUI 可能会失去响应。 【参考方案1】:

每个 OpenGL 小部件都有自己的OpenGL context,这些上下文是QObjects,因此可以移动到其他线程。与任何其他非线程安全对象一样,您只能从它们的thread() 访问它们。

此外 - 这也可以移植到 QML - 您可以使用工作函数来计算显示列表,然后将其提交给渲染线程以转换为绘图调用。渲染线程不做任何逻辑,也不计算任何东西:它获取数据(顶点数组等)并将其提交以进行绘制。工作函数将被提交以在使用 QtConcurrent::run 的线程池上执行。

因此,您可以拥有一个主线程、一个渲染线程(可能每个小部件一个,但不一定)和运行模拟步骤的函子。

无论如何,复杂的逻辑和渲染是一个非常糟糕的主意。无论您是在光栅窗口小部件上使用QPainter 进行绘图,还是在QOpenGLWidget 上使用QPainter,或者使用直接的OpenGL 调用,进行绘图的线程都不应该计算要绘制的内容。

如果您不想弄乱 OpenGL 调用,并且可以将大部分工作表示为基于数组的 QPainter 调用(例如 drawRectsdrawPolygons),这些几乎直接转化为 OpenGL 绘制调用并且 OpenGL 后端将像您手动编码绘图调用一样快速地渲染它们。如果您在QOpenGLWidget 上使用QPainter,它会为您完成这一切!

【讨论】:

为迟到的回复道歉。工作中挑剔的网络限制使我无法在 *** 上发帖,但无法阅读。 > 渲染线程不做任何逻辑,也不计算任何东西:它获取数据(顶点数组等)并将其提交以进行绘制。我认为不能将 QWidget 对象的绘图放到另一个线程中。如果是这样,那么渲染线程将如何为位于主线程中的小部件执行绘图? OpenGL QWidget 是一个特例。上面的画必须在widget.context()->thread()完成。您可以将上下文移动到任何线程。即使对于非 OpenGL 小部件,您也可以通过渲染到 QImage 然后在主线程中对该图像进行 blitting 来在另一个线程中进行绘制。

以上是关于将 Qt GUI 拆分为多个线程用于 GUI、模拟和 OpenGL 是不是可行?的主要内容,如果未能解决你的问题,请参考以下文章

qt多个线程调用同一个类怎么处理

由于线程问题,Qt Gui 未更新

qt 线程选项

qt如何解决线程空转的问题

正如我所料,Qt GUI 不适用于 std::thread

响应式 Qt GUI,即使是线程