在Qt中滚动期间如何避免感知闪烁?

时间:2012-04-16 11:36:17

标签: performance qt drawing flicker qpainter

我正在尝试使用Qt框架(4.7.4)来演示滑动显示,其中新的像素数据被添加到屏幕的第一行,并且先前的像素在每次刷新时滚动一个像素。

每秒刷新20次,每次刷新时,在黑色背景上绘制随机绿点(像素)。

问题是;每次更新都会有明显的闪烁。我已经通过网络进行了研究,并尽可能地优化了我的代码。我尝试使用QPainter(在QWidget上)和QGraphicsScene(在QGraphicsView上)进行光栅渲染,甚至我尝试在QGLWidget上使用OpenGL渲染。但是,最后我还有同样的闪烁问题。

什么可能导致这种闪烁?我开始怀疑我的液晶显示器无法刷新显示器的黑色到绿色过渡。我还注意到,如果我选择灰色背景而不是黑色,则不会发生闪烁。

2 个答案:

答案 0 :(得分:1)

你所看到的效果纯粹是心理视觉。这是人为缺陷,而不是软件缺陷。我是认真的。您可以通过修复x的值进行验证 - 您仍将重新绘制窗口上的整个像素图,不会有任何闪烁 - 因为本身没有闪烁。

当滚动率与实时通过无关时,会发生心理视觉闪烁。如果由于CPU负载或系统定时器不准确而导致更新之间的时间间隔发生变化,我们的视觉系统会集成两个图像,看起来好像整体亮度发生了变化。

您已经正确地注意到,通过将背景设置为灰色来降低图像的对比度,可以减少感知的闪烁。这是一个额外的线索,效果是心理视觉。

以下是一种防止这种影响的方法。注意滚动距离如何与时间相关联(此处:1ms = 1pixel)。

#include <QElapsedTimer>
#include <QPaintEvent>
#include <QBasicTimer>
#include <QApplication>
#include <QPainter>
#include <QPixmap>
#include <QWidget>
#include <QDebug>

static inline int rand(int range) { return (double(qrand()) * range) / RAND_MAX; }

class Widget : public QWidget
{
    float fps;
    qint64 lastTime;
    QPixmap pixmap;
    QBasicTimer timer;
    QElapsedTimer elapsed;
    void timerEvent(QTimerEvent * ev) {
        if (ev->timerId() == timer.timerId()) update();
    }
    void paintEvent(QPaintEvent * ev) {
        qint64 time = elapsed.elapsed();
        qint64 delta = time - lastTime;
        lastTime = time;
        if (delta > 0) {
            const float weight(0.05);
            fps = (1.0-weight)*fps + weight*(1E3/delta);

            if (pixmap.size() != size()) {
                pixmap = QPixmap(size());
                pixmap.fill(Qt::black);
            }
            int dy = qMin((int)delta, pixmap.height());
            pixmap.scroll(0, dy, pixmap.rect());
            QPainter pp(&pixmap);
            pp.fillRect(0, 0, pixmap.width(), dy, Qt::black);
            for(int i = 0; i < 30; ++i){
                int x = rand(pixmap.width());
                pp.fillRect(x, 0, 3, dy, Qt::green);
            }
        }
        QPainter p(this);
        p.drawPixmap(ev->rect(), pixmap, ev->rect());
        p.setPen(Qt::yellow);
        p.fillRect(0, 0, 100, 50, Qt::black);
        p.drawText(rect(), QString("FPS: %1").arg(fps, 0, 'f', 0));
    }

public:
    explicit Widget(QWidget *parent = 0) : QWidget(parent), fps(0), lastTime(0), pixmap(size())
    {
        timer.start(1000/60, this);
        elapsed.start();
        setAttribute(Qt::WA_OpaquePaintEvent);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

答案 1 :(得分:-1)

我建议你不要就地滚动像素图,但要创建第二个像素图,并使用drawPixmap()复制除pixmap 1到pixmap 2之外的所有内容(带有滚动偏移)。然后继续在pixmap 2上绘画。在帧之后,将引用交换到两个像素图,然后重新开始。

理由是,从一个存储区域复制到另一个存储区域比在原地修改一个存储区域更容易优化。