在QWidget上绘制基于像素的图形

3
我有一个应用程序,需要以指定的帧速率(模拟旧机器)逐像素绘制。其中一个注意点是主机引擎在后台线程中运行,以确保UI在模拟期间保持响应和可用性。
目前,我正在尝试使用类似以下代码的东西:
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_;
};

这个方法效果很好,而且速度惊人地快。然而,存在一个问题。更新函数会安排一个paintEvent,但它可能不会立即发生。实际上,根据Qt文档,一堆paintEvent可能被"合并"。
我看到的负面影响是,在模拟几分钟后,屏幕停止更新(图像似乎冻结,尽管模拟仍在运行),直到我做一些强制屏幕更新的事情,例如切换窗口大小。
我尝试使用QTimer等类似机制来使渲染效果在GUI线程中,以便我可以强制立即更新,但这导致了无法接受的性能问题。
有没有更好的方法在固定时间间隔内不断地将像素绘制到小部件上?最好使用纯Qt解决方案。

编辑:由于一些人选择摆出态度而不是阅读整个问题,我将澄清问题。我不能使用QWidget::repaint,因为它有一个限制,必须从与事件循环相同的线程中调用。否则,没有更新发生,而是会得到如下的qDebug消息:

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

编辑:为了演示问题,我创建了这个简单的示例代码:

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();
}

我肯定愿意探索不使用临时的QImage进行渲染的选项。它只是Qt中似乎唯一具有直接像素写入接口的类。


1
你可以使用排队的信号槽连接来确保update()函数从GUI线程中调用。 - ChrisV
@Noah:你的建议似乎不起作用,如果你能提供一个演示,在给定的例子中它确实有效,我会很高兴接受你的答案。 - Evan Teran
你看过这个答案吗?https://dev59.com/tknSa4cB1Zd3GeqPPZzP。 - Luis G. Costantini R.
+1 这似乎是朝着正确方向迈出的一步。我得检查一下是否存在同样的“更新停止”的问题。 - Evan Teran
显示剩余3条评论
2个回答

3
尝试从线程中向事件循环小部件中的槽发送信号,该槽调用repaint(),然后立即执行。我在我的图形程序中做了类似的事情,在一个线程中执行主要计算,然后告诉小部件何时重新绘制数据。

1
在类似的情况下,我所做的是仍然使用QTimer,但是进行多个模拟步骤而不仅仅是一个。您甚至可以使程序自动调整模拟步骤的数量,以便能够获得所需的屏幕更新每秒帧数。

QTimer的方法似乎可行,但我在使用过程中遇到了性能问题,我会重新考虑一下。也许有些地方我忽略了。 - Evan Teran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接