C++ QQuickPaintedItem使用QPainter缓慢更新
所以我一直在玩弄arpund,用C++ QQuickPaintedItem使用QPainter缓慢更新,c++,qt,qml,qt5,C++,Qt,Qml,Qt5,所以我一直在玩弄arpund,用QQuickPaintedItem制作一个简单的涂鸦应用程序。从中汲取经验,我基于QQuickPaintedItem创建了一个简单的QML项,它获取鼠标事件并根据收到的输入在屏幕上绘制路径。然而,在测试后,我意识到我的实现很慢,更具体地说,当鼠标在场景中移动时,绘画滞后于移动。我必须使用一个定制的QWidget(使用相同的技术)创建相同的示例,结果要好得多,在绘制过程中几乎没有延迟。 我已经记录了这个问题(速度降低到0.5倍):vs 以下是QQuickPainte
QQuickPaintedItem
制作一个简单的涂鸦应用程序。从中汲取经验,我基于QQuickPaintedItem
创建了一个简单的QML项,它获取鼠标事件并根据收到的输入在屏幕上绘制路径。然而,在测试后,我意识到我的实现很慢,更具体地说,当鼠标在场景中移动时,绘画滞后于移动。我必须使用一个定制的QWidget
(使用相同的技术)创建相同的示例,结果要好得多,在绘制过程中几乎没有延迟。
我已经记录了这个问题(速度降低到0.5倍):vs
以下是QQuickPaintedItem
实现的代码:
绘制画布
#ifndef DRAWINGCANVAS_H
#define DRAWINGCANVAS_H
#include <QObject>
#include <QQuickPaintedItem>
#include <QImage>
#include <QPainter>
class DrawingCanvas : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(bool drawing READ drawing WRITE setDrawing NOTIFY drawingChanged)
public:
explicit DrawingCanvas(QQuickItem *parent = nullptr);
bool drawing() const;
Q_INVOKABLE void initiateBuffer();
QString penColor() const;
public slots:
void setDrawing(bool drawing);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void paint(QPainter *painter);
signals:
void drawingChanged(bool drawing);
void penWidthChanged(int penWidth);
void penColorChanged(QString penColor);
private:
void drawOnBuffer(QPointF pos);
bool m_drawing;
QPixmap m_buffer;
QPointF m_lastPoint;
QRect m_updateRect;
};
#endif // DRAWINGCANVAS_H
DrawingWidget.cpp
#include "drawingwidget.h"
#include <QCoreApplication>
#include <QPainter>
#include <QtMath>
#include <cstdlib>
#include <QMouseEvent>
DrawingWidget::DrawingWidget()
{
resize(500, 500);
setAutoFillBackground(true);
}
void DrawingWidget::mousePressEvent(QMouseEvent *event)
{
if (!m_deviceDown) {
m_deviceDown = true;
lastPoint.pos = event->pos();
}
}
void DrawingWidget::mouseMoveEvent(QMouseEvent *event)
{
if (m_deviceDown) {
QPainter painter(&m_pixmap);
paintPixmap(painter, event);
lastPoint.pos = event->pos();
}
}
void DrawingWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (m_deviceDown && event->buttons() == Qt::NoButton)
m_deviceDown = false;
update();
}
void DrawingWidget::initPixmap()
{
qreal dpr = devicePixelRatioF();
QPixmap newPixmap = QPixmap(qRound(width() * dpr), qRound(height() * dpr));
newPixmap.setDevicePixelRatio(dpr);
newPixmap.fill(Qt::white);
QPainter painter(&newPixmap);
if (!m_pixmap.isNull())
painter.drawPixmap(0, 0, m_pixmap);
painter.end();
m_pixmap = newPixmap;
}
void DrawingWidget::paintEvent(QPaintEvent *event)
{
if (m_pixmap.isNull())
initPixmap();
QPainter painter(this);
QRect pixmapPortion = QRect(event->rect().topLeft() * devicePixelRatioF(),
event->rect().size() * devicePixelRatioF());
painter.drawPixmap(event->rect().topLeft(), m_pixmap, pixmapPortion);
}
void DrawingWidget::paintPixmap(QPainter &painter, QMouseEvent *event)
{
static qreal maxPenRadius = 1.0;
painter.setRenderHint(QPainter::Antialiasing);
painter.drawLine(lastPoint.pos, event->pos());
update(QRect(lastPoint.pos.toPoint(), event->pos()).normalized()
.adjusted(-maxPenRadius, -maxPenRadius, maxPenRadius, maxPenRadius));
}
void DrawingWidget::resizeEvent(QResizeEvent *)
{
initPixmap();
}
好的,所以在玩了一个多月的代码之后,我意识到这是因为QML中启用了V-Sync。默认情况下,QML自动将总账图形与屏幕的垂直刷新同步。描述了该问题的解决方案,其中提到: 为了最大限度地减少延迟,您最好的选择是:
QSurfaceFormat::setSwapInterval(0)
(自5.3版起)或在系统的控制面板中禁用vsync
basic
,则QML动画系统在CPU上会变得非常困难,动画速度会变得太快。要在Qt中设置环境变量,只需调用:
qputenv("QSG_RENDER_LOOP", "basic");
在实例化Qt应用程序对象之前。如果您对性能有疑问,则必须使用
QQuickItem
,QQuickPaintedItem
起双重作用。@folibis QML Shape API基本上使用了这一点,性能仍然比QWidget
差,我不明白在这里使用QPixmap
的目的。您可以维护定义为QVector
的多段线,然后使用QPainter
的drawPolyline
方法:@pinebit这样做是因为绘制pixmap比重新绘制点快。@daljit97如果您仔细检查QQuickPaintedItem的文档,您会发现它已经在使用备份图像(缓冲区)用于绘画。因此,使用pixmap,基本上可以添加另一个图像缓冲区。还要注意的是,绘制多边形将转换为OpenGL调用,因此,如果您的QML使用OpenGL进行渲染,那么它将被硬件加速。试一试,看看速度有多快。
#include "drawingwidget.h"
#include <QCoreApplication>
#include <QPainter>
#include <QtMath>
#include <cstdlib>
#include <QMouseEvent>
DrawingWidget::DrawingWidget()
{
resize(500, 500);
setAutoFillBackground(true);
}
void DrawingWidget::mousePressEvent(QMouseEvent *event)
{
if (!m_deviceDown) {
m_deviceDown = true;
lastPoint.pos = event->pos();
}
}
void DrawingWidget::mouseMoveEvent(QMouseEvent *event)
{
if (m_deviceDown) {
QPainter painter(&m_pixmap);
paintPixmap(painter, event);
lastPoint.pos = event->pos();
}
}
void DrawingWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (m_deviceDown && event->buttons() == Qt::NoButton)
m_deviceDown = false;
update();
}
void DrawingWidget::initPixmap()
{
qreal dpr = devicePixelRatioF();
QPixmap newPixmap = QPixmap(qRound(width() * dpr), qRound(height() * dpr));
newPixmap.setDevicePixelRatio(dpr);
newPixmap.fill(Qt::white);
QPainter painter(&newPixmap);
if (!m_pixmap.isNull())
painter.drawPixmap(0, 0, m_pixmap);
painter.end();
m_pixmap = newPixmap;
}
void DrawingWidget::paintEvent(QPaintEvent *event)
{
if (m_pixmap.isNull())
initPixmap();
QPainter painter(this);
QRect pixmapPortion = QRect(event->rect().topLeft() * devicePixelRatioF(),
event->rect().size() * devicePixelRatioF());
painter.drawPixmap(event->rect().topLeft(), m_pixmap, pixmapPortion);
}
void DrawingWidget::paintPixmap(QPainter &painter, QMouseEvent *event)
{
static qreal maxPenRadius = 1.0;
painter.setRenderHint(QPainter::Antialiasing);
painter.drawLine(lastPoint.pos, event->pos());
update(QRect(lastPoint.pos.toPoint(), event->pos()).normalized()
.adjusted(-maxPenRadius, -maxPenRadius, maxPenRadius, maxPenRadius));
}
void DrawingWidget::resizeEvent(QResizeEvent *)
{
initPixmap();
}
Shape {
id: myShape
anchors.fill: parent
ShapePath {
id: shapePath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
}
}
MouseArea {
anchors.fill: parent
onPressed: {
shapePath.startX = mouse.x
shapePath.startY = mouse.y
}
onPositionChanged: {
var pathcurve = Qt.createQmlObject(
'import QtQuick 2.12; PathCurve {}', shapePath)
pathcurve.x = mouse.x
pathcurve.y = mouse.y
shapePath.pathElements.push(pathcurve)
}
}
qputenv("QSG_RENDER_LOOP", "basic");