使用QPainter缓慢更新QQuickPaintedItem

时间:2020-05-14 10:41:09

标签: c++ qt qml qt5

因此,我一直在用QQuickPaintedItem玩arpund来制作一个简单的涂鸦应用。出于对Tablet Example的启发,我基于QQuickPaintedItem创建了一个简单的QML项目,该项目获取鼠标事件并根据收到的输入在屏幕上绘制路径。但是,通过测试,我意识到我的实现很慢,尤其是当鼠标在场景中移动时,涂料会滞后于运动。我必须使用自定义QWidget(使用相同的技术)来创建相同的示例,并且结果要好得多,而且绘画几乎没有滞后。 我已经记录了该问题(放慢了0.5倍):Video QQuickPaintedItemVideo QWidget

以下是QQuickPaintedItem实现的代码:

DrawingCanvas.h

#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

DrawingCanvas.cpp

#include "drawingcanvas.h"

#include <QPainter>

DrawingCanvas::DrawingCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
{
    setRenderTarget(FramebufferObject);
    setAcceptedMouseButtons(Qt::AllButtons);
}

bool DrawingCanvas::drawing() const
{
    return m_drawing;
}

void DrawingCanvas::mousePressEvent(QMouseEvent *event)
{
    if (!m_drawing) {
        m_drawing = true;
        m_lastPoint = event->pos();
    }
}

void DrawingCanvas::mouseMoveEvent(QMouseEvent *event)
{
    if (m_drawing) {
        drawOnBuffer(event->pos());
        m_lastPoint = event->pos();
    }
}

void DrawingCanvas::mouseReleaseEvent(QMouseEvent *event)
{
    if (m_drawing && event->buttons() == Qt::NoButton)
        m_drawing = false;
}

void DrawingCanvas::paint(QPainter *painter)
{
    painter->drawPixmap(m_updateRect, m_buffer, m_updateRect);
    m_updateRect = QRect();
}

void DrawingCanvas::setDrawing(bool drawing)
{
    if (m_drawing == drawing)
        return;

    m_drawing = drawing;
    emit drawingChanged(m_drawing);
}

void DrawingCanvas::initiateBuffer()
{
    m_buffer = QPixmap(width(), height());
    m_buffer.fill(Qt::transparent);
}

void DrawingCanvas::drawOnBuffer(QPointF pos)
{
    QPainter bufferPainter;
    int rad = 2;

    if(bufferPainter.begin(&m_buffer)){

        bufferPainter.drawLine(m_lastPoint, pos);

        auto dirtyRect = QRect(m_lastPoint.toPoint(), pos.toPoint()).normalized()
                .adjusted(-rad, -rad, rad, rad);


        //        // change the canvas dirty region
        if(m_updateRect.isNull()){
            m_updateRect = dirtyRect;
        }
        else{
            m_updateRect = m_updateRect.united(dirtyRect);
        }
        update(m_updateRect);
    }
}

这是QWidget的实现:

DrawingWidget.h

#ifndef DRAWINGWIDGET_H
#define DRAWINGWIDGET_H

#include <QWidget>

class DrawingWidget : public QWidget
{
    Q_OBJECT
public:
    DrawingWidget();
protected:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;

private:
    void initPixmap();
    void paintPixmap(QPainter &painter, QMouseEvent *event);

    QPixmap m_pixmap;
    bool m_deviceDown = false;

    struct Point {
        QPointF pos;
    } lastPoint;
};

#endif // DRAWINGWIDGET_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中的新Shapes API来实现这一点,但是结果类似于QQuickPaintedItem

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

1 个答案:

答案 0 :(得分:2)

好吧,在玩了一个多月的代码之后,我意识到这是因为QML中启用了V-Sync。默认情况下,QML自动将GL绘图同步到屏幕的垂直刷新。 here描述了该问题的解决方案,其中提到:

为了最大程度地减少延迟,最好的选择是:

  1. 使用QSurfaceFormat::setSwapInterval(0)(从5.3开始)或在系统控制面板中禁用垂直同步。

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

Qt的鼠标输入处理是通过在GUI线程上发布一个偶数来完成的 当发生鼠标/触摸事件时,它将在下一个中显示 渲染的框架。使用vsync和double / triple缓冲,这意味着 事件发生后0-33毫秒,画面出现在屏幕上。最多 如果应用已被限制,则将添加16ms延迟(并且 例如,它的swapBuffer()调用被阻止了。)

然后添加系统合成器添加的任何延迟,这可能或 可能不是值得延迟的其他几个vsync。

对于第1步,可以在加载QML引擎之前使用非常方便的QSurfaceFormat::setDefaultFormat。 步骤2非常重要,因为如果未将渲染循环设置为basic,则QML动画系统会在CPU上发疯,并且动画变得太快。要在Qt中设置环境变量,只需调用:

qputenv("QSG_RENDER_LOOP", "basic");

在实例化Qt Application对象之前。