4

I am experimenting with Qt for a new layout for an instrument simulation program at work. 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. 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. I am working under a few big constraints:

  1. Each of the instrument panels still need to be in their OpenGL viewport. There are a lot of buttons and a lot of instruments. My tentative solution is to use a QOpenGLWidget for each. 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. 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.
  3. Our customers may have old computers and thus more recent versions of OpenGL have been ruled out. 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. I don't yet know if OpenGL 3 is on the table, which will be necessary (I think) for rendering to off-screen buffers.

Problem: QOpenGLWidget does not have on overrideable "update" method, and it only draws during the widgets' paintEvent(...) and paintGL(...) calls.

Tentative Solution: Split the simulator into three threads:

  1. GUI: Runs user input, paintEvent(...), and 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)).

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. 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?

If feasible, any other caution or suggestions would be appreciated.

Community
  • 1
  • 1
John Cox
  • 339
  • 1
  • 3
  • 15
  • 3
    Usually all drawing and widget manipulation needs to be in the thread that owns the "window", this goes for both normal GUI widgets and windows, and for OpenGL. Everything else can be in threads. You can modify objects being drawn by e.g. OpenGL in another thread, just not do the drawing on another thread. Also be careful so you don't modify an object that is just being drawn. – Some programmer dude Aug 19 '16 at 00:42
  • Why do you need such a design? Event queues in GUI systems are just that, queues. You will not miss an event unless you're in a specialized modal mode. This makes it perfectly reasonable, and for input latency reasons the norm, to allow rendering to block input processing. – Andon M. Coleman Aug 19 '16 at 19:07
  • Because the event handling is blocking on the thread and I didn't want to tie up event handling with a paintEvent(...) that can last a long time. I was under the impression that, if the event system is tied up like that, then the GUI may loose responsiveness. – John Cox Aug 20 '16 at 19:19

1 Answers1

5

Each OpenGL widget has its own OpenGL context, and these contexts are QObjects and thus can be moved to other threads. As with any otherwise non-threadsafe object, you should only access them from their 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. 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.

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.

If you don't want to mess with OpenGL calls, and you can represent most of your work as array-based QPainter calls (e.g. 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. QPainter does all this for you if you use it on a QOpenGLWidget!

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Apologize for the late reply. Picky network restrictions at work prevent me from posting on stackoverflow but not from reading it. > The render thread doesn't do any logic and doesn't compute anything: it takes data (vertex arrays, etc.) and submits it for drawing. I thought that QWidget objects' drawing could not be put off into another thread. If so, then how would a render thread perform the drawing for a widget that lives in the main thread? – John Cox Aug 20 '16 at 19:21
  • An OpenGL `QWidget` is a special case. The painting on it must be done in the `widget.context()->thread()`. You can move the context to any thread. Even for a non-OpenGL widget you can do painting in another thread by rendering to a `QImage` and then blitting that image in the main thread. – Kuba hasn't forgotten Monica Aug 22 '16 at 18:13