简体   繁体   English

将Qt GUI拆分为GUI,模拟和OpenGL的多个线程是否可行?

[英]Is it feasible to split Qt GUI into multiple threads for GUI, simulation, and OpenGL?

I am experimenting with Qt for a new layout for an instrument simulation program at work. 我正在试验Qt的工作中的仪器模拟程序的新布局。 Our current sim is running everything in a single window (we've used both glut (old) and fltk), it uses glViewport(...) and glScissor(...) to split instrument readouts into their own views, and then it uses some form of "ortho2D" calls to create their own virtual pixel space. 我们当前的sim在一个窗口中运行所有内容(我们使用了glut(旧)和fltk),它使用glViewport(...)glScissor(...)将仪器读数分成他们自己的视图,然后它使用某种形式的“ortho2D”调用来创建自己的虚拟像素空间。 The simulator currently updates the instruments and then draws each in their own viewport one by one, all in the same thread. 模拟器当前更新仪器,然后逐个在各自的视口中绘制每个仪器,所有这些都在同一个线程中。

We want to find a better approach, and we settled on Qt. 我们想找到一个更好的方法,我们选择了Qt。 I am working under a few big constraints: 我正在一些很大的限制下工作:

  1. Each of the instrument panels still need to be in their OpenGL viewport. 每个仪表板仍然需要在其OpenGL视口中。 There are a lot of buttons and a lot of instruments. 有很多按钮和很多乐器。 My tentative solution is to use a QOpenGLWidget for each. 我的初步解决方案是为每个使用QOpenGLWidget。 I have made progress on this. 我在这方面取得了进展。
  2. The sim is not just a pretty readout, but also simulates many of the instruments as feedback for the instrument designers, so it sometimes has a hefty CPU load. SIM不仅仅是一个非常好的读数,而且还模拟了许多仪器作为仪器设计人员的反馈,因此它有时会有很大的CPU负载。 It isn't a full hardware emulator, but it does simulate the logic. 它不是一个完整的硬件模拟器,但它确实模拟了逻辑。 I don't think that it's feasible to tell the instruments to update themselves at the beginning of its associated widget's paintEvent(...) method, so I want simulation updates to run in a separate thread. 我不认为告诉仪器在其关联的小部件的paintEvent(...)方法的开头更新自己是可行的,所以我希望模拟更新在单独的线程中运行。
  3. Our customers may have old computers and thus more recent versions of OpenGL have been ruled out. 我们的客户可能拥有旧计算机,因此排除了更新版本的OpenGL。 We are still using glBegin() and glEnd() and everything in between, and the instruments draw a crap ton of variable symbols, so drawing is takes a lot of time and I want to split drawing off into it's own thread. 我们仍然使用glBegin()glEnd()以及介于两者之间的所有东西,并且仪器绘制了一堆垃圾变量符号,因此绘制需要花费大量时间,我想将绘制分割成它自己的线程。 I don't yet know if OpenGL 3 is on the table, which will be necessary (I think) for rendering to off-screen buffers. 我还不知道OpenGL 3是否在桌面上,这对于渲染到屏幕外缓冲区是必要的(我认为)。

Problem: QOpenGLWidget does not have on overrideable "update" method, and it only draws during the widgets' paintEvent(...) and paintGL(...) calls. 问题: QOpenGLWidget没有可重写的“update”方法,它只在窗口小部件的paintEvent(...)paintGL(...)调用期间绘制。

Tentative Solution : Split the simulator into three threads: 暂定解决方案 :将模拟器拆分为三个线程:

  1. GUI: Runs user input, paintEvent(...) , and paintGL(...) . GUI:运行用户输入, paintEvent(...)paintGL(...)
  2. Simulator: Runs all instrument logic and updates values for symbology. 模拟器:运行所有仪器逻辑并更新符号系统的值。
  3. Drawing: Renders latest symbology to an offscreen buffer (will use a frame buffer object (FBO)). 绘图:将最新的符号系统渲染到屏幕外缓冲区(将使用帧缓冲区对象(FBO))。

In this design, cross-thread talking is cyclic and one-way, with the GUI thread providing input, the simulator thread taking that input into account on its next loop, the drawing thread reading the latest symbology and rendering it to the FBO and setting a "next frame available" flag to true (or maybe emitting a signal), and then the paintGL(...) method will take that FBO and spit it out to the widget, thus keeping event processing down and GUI responsiveness up. 在这个设计中,跨线程说话是循环的,单向的,GUI线程提供输入,模拟器线程在下一个循环中考虑该输入,绘图线程读取最新的符号系统并将其呈现给FBO并设置“下一帧可用”标志为true(或者可能发出信号),然后paintGL(...)方法将该FBO并将其吐出到小部件,从而保持事件处理能力下降和GUI响应能力提高。 Continue this cycle. 继续这个循环。

Bottom line question : I've read here that GUI operations cannot be done in a separate thread, so is my approach even feasible? 底线问题 :我在这里读到GUI操作无法在单独的线程中完成,所以我的方法是否可行?

If feasible, any other caution or suggestions would be appreciated. 如果可行,任何其他谨慎或建议将不胜感激。

Each OpenGL widget has its own OpenGL context , and these contexts are QObject s and thus can be moved to other threads. 每个OpenGL小部件都有自己的OpenGL上下文 ,这些上下文是QObject ,因此可以移动到其他线程。 As with any otherwise non-threadsafe object, you should only access them from their thread() . 与任何其他非线程安全对象一样,您只应从其thread()访问它们。

Additionally - and this is also portable to QML - you could use worker functors to compute display lists that are then submitted to the render thread to be converted into draw calls. 另外 - 这也可以移植到QML - 您可以使用工作器函数来计算显示列表,然后将其提交到渲染线程以转换为绘制调用。 The render thread doesn't do any logic and doesn't compute anything: it takes data (vertex arrays, etc.) and submits it for drawing. 渲染线程不做任何逻辑并且不计算任何东西:它接受数据(顶点数组等)并提交它以进行绘制。 The worker functors would be submitted for execution on a thread pool using QtConcurrent::run . 工作QtConcurrent::run函数将使用QtConcurrent::run提交在线程池上QtConcurrent::run

You can thus have a main thread, a render thread (perhaps one per widget, but not necessarily), and functors that run your simulation steps. 因此,您可以拥有一个主线程,一个渲染线程(可能每个小部件一个,但不一定),以及运行模拟步骤的仿函数。

In any case, convoluting logic and rendering is a very bad idea . 无论如何,卷积逻辑和渲染是一个非常糟糕的主意 Whether you're doing drawing using QPainter on a raster widget, or using QPainter on an QOpenGLWidget , or using direct OpenGL calls, the thread that does the drawing should not have to compute what's to be drawn. 无论您是在栅格小部件上使用QPainter进行绘制,还是在QOpenGLWidget上使用QPainter ,或者使用直接OpenGL调用,执行绘图的线程都不应该计算要绘制的内容。

If you don't want to mess with OpenGL calls, and you can represent most of your work as array-based QPainter calls (eg drawRects , drawPolygons ), these translate almost directly into OpenGL draw calls and the OpenGL backend will render them just as quickly as if you hand-coded the draw calls. 如果您不想搞乱OpenGL调用,并且您可以将大部分工作表示为基于数组的QPainter调用(例如drawRectsdrawPolygons ),则这些调用几乎直接转换为OpenGL绘制调用,而OpenGL后端将使它们呈现为很快,好像你手动编写了绘制调用。 QPainter does all this for you if you use it on a QOpenGLWidget ! 如果你在QOpenGLWidget上使用它, QPainter会为你做这QOpenGLWidget

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM