C++ QQuickPaintedItem使用QPainter缓慢更新

C++ QQuickPaintedItem使用QPainter缓慢更新,c++,qt,qml,qt5,C++,Qt,Qml,Qt5,所以我一直在玩弄arpund,用QQuickPaintedItem制作一个简单的涂鸦应用程序。从中汲取经验,我基于QQuickPaintedItem创建了一个简单的QML项,它获取鼠标事件并根据收到的输入在屏幕上绘制路径。然而,在测试后,我意识到我的实现很慢,更具体地说,当鼠标在场景中移动时,绘画滞后于移动。我必须使用一个定制的QWidget(使用相同的技术)创建相同的示例,结果要好得多,在绘制过程中几乎没有延迟。 我已经记录了这个问题(速度降低到0.5倍):vs 以下是QQuickPainte

所以我一直在玩弄arpund,用
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

  • 使用QSG_RENDER_LOOP=basic运行应用程序以关闭线程/窗口渲染循环。windows/线程化渲染循环将 依靠vsync进行节流,因此如果不设置“基本”动画 将以100%的CPU速度旋转

  • Qt的鼠标输入处理是通过向GUI线程发送偶数来完成的 当鼠标/触摸事件进入时。这将在下一个星期出现 渲染帧。对于vsync和双/三重缓冲,这意味着 事件发生后0-33毫秒,画面进入屏幕。取决于另一个 如果应用程序已被限制(以及 例如在其swapBuffer()调用中被阻止)

    然后添加系统合成器添加的任何延迟,该延迟可能 可能不会再有几个VSync值得延迟

    对于步骤1,可以在加载QML引擎之前使用非常方便的。 步骤2非常重要,因为如果渲染循环未设置为
    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");