3

I have an application which needs to draw on a pixel by pixel basis at a specified frame rate (simulating an old machine). One caveat is that the main machine engine runs in a background thread in order to ensure that the UI remains responsive and usable during simulation.

Currently, I am toying with using something like this:

class QVideo : public QWidget {
public:
    QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {

    }

    void draw_frame(void *data) {
        // render data into screen_image_
    }

    void start_frame() {
        // do any pre-rendering prep work that needs to be done before
        // each frame
    }

    void end_frame() {
        update(); // force a paint event
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.drawImage(rect(), screen_image_, screen_image_.rect());
    }

    QImage screen_image_;
};

This is mostly effective, and surprisingly not very slow. However, there is an issue. The update function schedules a paintEvent, it may not hapen right away. In fact, a bunch of paintEvent's may get "combined" according to the Qt documentation.

The negative effect that I am seeing is that after a few minutes of simulation, the screen stops updating (image appears frozen though simulation is still running) until I do something that forces a screen update for example switching the window in and out of maximized.

I have experimented with using QTimer's and other similar mechanism to have the effect of the rendering being in the GUI thread so that I can force immediate updates, but this resulted in unacceptable performance issues.

Is there a better way to draw pixels onto a widget constantly at a fixed interval. Pure Qt solutions are preferred.

EDIT: Since some people choose to have an attitude instead of reading the whole question, I will clarify the issue. I cannot use QWidget::repaint because it has a limitation in that it must be called from the same thread as the event loop. Otherwise, no update occurs and instead I get qDebug messages such as these:

QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent

EDIT: to demonstrate the issue I have created this simple example code:

QVideo.h

#include <QWidget>
#include <QPainter>

class QVideo : public QWidget {
    Q_OBJECT;
public:
    QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {

    }

    void draw_frame(void *data) {
        // render data into screen_image_
        // I am using fill here, but in the real thing I am rendering
        // on a pixel by pixel basis
        screen_image_.fill(rand());
    }

    void start_frame() {
        // do any pre-rendering prep work that needs to be done before
        // each frame
    }

    void end_frame() {
        //update(); // force a paint event
        repaint();
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.drawImage(rect(), screen_image_, screen_image_.rect());
    }

    QImage screen_image_;
};

main.cc:

#include <QApplication>
#include <QThread>
#include <cstdio>
#include "QVideo.h"


struct Thread : public QThread {

    Thread(QVideo *v) : v_(v) {
    }

    void run() {
        while(1) {
            v_->start_frame();
            v_->draw_frame(0); // contents doesn't matter for this example
            v_->end_frame();
            QThread::sleep(1);
        }
    }

    QVideo *v_;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QVideo w;
    w.show();

    Thread t(&w);
    t.start();

    return app.exec();
}

I am definitely willing to explore options which don't use a temporary QImage to render. It is just the only class in Qt which seems to have a direct pixel writing interface.

Community
  • 1
  • 1
Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • 1
    You could use a queued signal-slot connection to ensure update() gets called from the GUI thread. – ChrisV Dec 09 '10 at 22:08
  • @Noah: First of all, you are the one who started with an attitude. What I need to do is draw pixels which is why I talked about So if there is an alternate solution to what I am doing, I am interested. The code I am using is not using a `QPixmap`, so clearly it is being used internally in Qt. Your suggestion does not seem to be workable given my example. I will try to produce a simple example demonstrating this. – Evan Teran Dec 09 '10 at 22:10
  • @Noah: Your suggestion does not appear to work, if you can come up with a demonstration where it does work given the example, I'll be happy to accept your answer. – Evan Teran Dec 09 '10 at 22:12
  • @ChrisV - repaint(). OP needs repaint(). It also is a slot, like it says in the docs. – Edward Strange Dec 09 '10 at 22:14
  • @Noah: If the solution is to connect a signal to the repaint slot and emit that, then I will look into that, but you didn't suggest that, you basically just said "RTFM" which is unhelpful hense the downvote. If this is the best that can be done with Qt when having a separate thread produce the data that's fine. I was fully aware of repaint() before your answer and using it instead of update directly results in errors. If you have a suggestion on how to address that, I'd love to hear it. At no point did you suggest anything useful (like Luis did). – Evan Teran Dec 09 '10 at 23:48
  • possible duplicate of [Painting Issue on QWidget outsite GUI thread](http://stackoverflow.com/questions/1508151/painting-issue-on-qwidget-outsite-gui-thread) –  Mar 05 '14 at 09:29
  • Have you see this answer ? http://stackoverflow.com/questions/1508151/painting-issue-on-qwidget-outsite-gui-thread. – Luis G. Costantini R. Dec 09 '10 at 22:38
  • +1 this appears to be a step in the right direction. I'll have to check to see if this suffers from the same "updates stop happening" issue. – Evan Teran Dec 09 '10 at 23:48

2 Answers2

3

Try emitting a signal from the thread to a slot in the event loop widget that calls repaint(), which will then execute right away. I am doing something like this in my graphing program, which executes the main calculations in one thread, then tells the widget when it is time to repaint() the data.

Darren
  • 253
  • 1
  • 9
1

In similar cases what I did was still using a QTimer, but doing several simulation steps instead of just one. You can even make the program auto-tuning the number of simulation steps to be able to get whatever frames per seconds you like for the screen update.

6502
  • 112,025
  • 15
  • 165
  • 265
  • The QTimer approach seems workable, but I had performance issues with that, I'll revisit it. Perhaps there is something that I overlooked. – Evan Teran Dec 09 '10 at 23:42