双缓冲
1.双缓冲原理
单缓冲:在paintEvent中直接绘制到屏幕,绘制过程被用户看到
双缓冲:先在redrawBuffer绘制到缓冲区,然后一次性显示完整结果
代码结构
单缓冲:所有绘制逻辑在paintEvent中
双缓冲:绘制逻辑分离到redrawBuffer,paintEvent仅负责显示缓冲区
性能影响
单缓冲:每次更新需要重复计算和绘制
双缓冲:可以优化为只在必要时重绘缓冲区(如数据变化时)
减少的时间是重绘的那部分时间,单缓冲是逐个绘制,双缓冲是整体绘制。
测试案例
1.Qt 实现绘制 5000 个小球移动,单缓冲和双缓冲效果对比
双缓冲和单缓冲效果对比
2.代码
关键代码对比:
单缓冲绘制: paintEvent 中通过 painter 绘制每一个 circle,一次 paintEvent 绘制 5000 次
双缓冲绘制: paintEvent 中直接绘制提前赋好像素值的成员变量 buffer 中的像素
doublebufferwidget.h
#include <QWidget>
#include <QPainter>
#include <QTimer>
#include <QPixmap>
#include <QPointF>
#include <QVector>
#include <cmath>
#include <QTime>
#include <QDebug>
class DoubleBufferWidget : public QWidget {
Q_OBJECT
public:
DoubleBufferWidget(QWidget *parent = nullptr) : QWidget(parent) {
// 初始化缓冲区
setWindowTitle("DoubleBuffer");
buffer = QPixmap(size());
buffer.fill(Qt::white);
// 初始化动画数据
for (int i = 0; i < 5000; ++i) {
circles.append({
QPointF(rand() % width(), rand() % height()),
5.f + rand() % 10,
QColor(rand() % 256, rand() % 256, rand() % 256),
QPointF((rand() % 100 - 50) / 100.0, (rand() % 100 - 50) / 100.0)
});
}
// 设置定时器更新动画
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &DoubleBufferWidget::updateAnimation);
timer->start(16);
}
void resizeEvent(QResizeEvent *event) override {
buffer = QPixmap(size());
buffer.fill(Qt::white);
QWidget::resizeEvent(event);
}
void paintEvent(QPaintEvent *event) override {
QTime doublePaint;
doublePaint.start();
Q_UNUSED(event);
QPainter painter(this);
painter.drawPixmap(0, 0, buffer);
qInfo() << "void paintEvent(QPaintEvent *event) doublePaint cost times:" << doublePaint.elapsed() << "ms";
}
private slots:
void updateAnimation() {
// 更新所有圆形位置
for (auto &circle : circles) {
circle.pos += circle.velocity;
if (circle.pos.x() - circle.radius < 0 || circle.pos.x() + circle.radius > width())
circle.velocity.setX(-circle.velocity.x());
if (circle.pos.y() - circle.radius < 0 || circle.pos.y() + circle.radius > height())
circle.velocity.setY(-circle.velocity.y());
}
// 重新绘制到缓冲区
redrawBuffer();
update();
}
private:
struct Circle {
QPointF pos;
qreal radius;
QColor color;
QPointF velocity;
};
QPixmap buffer;
QVector<Circle> circles;
void redrawBuffer() {
buffer.fill(Qt::white);
QPainter bufferPainter(&buffer);
bufferPainter.setRenderHint(QPainter::Antialiasing);
// 模拟长时间绘制过程
for (int i = 0; i < 1000; ++i) {
double temp = std::sin(i);
Q_UNUSED(temp);
}
// 绘制所有圆形
for (const auto &circle : circles) {
bufferPainter.setPen(Qt::NoPen);
bufferPainter.setBrush(circle.color);
bufferPainter.drawEllipse(circle.pos, circle.radius, circle.radius);
}
}
};
singlebufferwidget.h
#include <QWidget>
#include <QTime>
#include <QPainter>
#include <QTimer>
#include <math.h>
#include <qmath.h>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class SingleBufferWidget; }
QT_END_NAMESPACE
class SingleBufferWidget : public QWidget {
Q_OBJECT
public:
SingleBufferWidget(QWidget *parent = nullptr) : QWidget(parent) {
// 初始化动画数据
for (int i = 0; i < 5000; ++i) {
circles.append({
QPointF(rand() % width(), rand() % height()),
5.f + rand() % 10,
QColor(rand() % 256, rand() % 256, rand() % 256),
QPointF((rand() % 100 - 50) / 100.0, (rand() % 100 - 50) / 100.0)
});
}
// 设置定时器更新动画
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &SingleBufferWidget::updateAnimation);
timer->start(16);
}
void paintEvent(QPaintEvent *event) override {
QTime SingpaintTimer;
SingpaintTimer.start();
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 模拟长时间绘制过程
for (int i = 0; i < 1000; ++i) {
// 这个循环会延长绘制时间,加剧闪烁现象
double temp = std::sin(i);
Q_UNUSED(temp);
}
// 绘制所有圆形
for (const auto &circle : circles) {
painter.setPen(Qt::NoPen);
painter.setBrush(circle.color);
painter.drawEllipse(circle.pos, circle.radius, circle.radius);
}
qInfo() << "void paintEvent(QPaintEvent *event) SingpaintTimer cost times:" << SingpaintTimer.elapsed() << "ms";
}
private slots:
void updateAnimation() {
// 更新所有圆形位置
for (auto &circle : circles) {
circle.pos += circle.velocity;
if (circle.pos.x() - circle.radius < 0 || circle.pos.x() + circle.radius > width())
circle.velocity.setX(-circle.velocity.x());
if (circle.pos.y() - circle.radius < 0 || circle.pos.y() + circle.radius > height())
circle.velocity.setY(-circle.velocity.y());
}
update();
}
private:
struct Circle {
QPointF pos;
qreal radius;
QColor color;
QPointF velocity;
};
QVector<Circle> circles;
};