c++ - QQuickPaintedItem 使用 QPainter 缓慢更新

标签 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

绘图 Canvas .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)
        }
    }

最佳答案

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

To minimize latency, your best bet would be to:

  1. disable vsync, either using QSurfaceFormat::setSwapInterval(0) (since 5.3) or in the system's control panel.

  2. Run your application with QSG_RENDER_LOOP=basic to turn off the threaded/window render loops. The windows/threaded render loops will rely on vsync to be throttled so if you don't set 'basic' animations will spin at 100% CPU.

Qt's mouse input handling is done by posting an even to the GUI thread when the mouse/touch event comes in. This will be present in the next rendered frame. With vsync and double/triple buffering this means that frame comes to screen 0-33 ms after the event occured. Up to another 16ms delay will be added if the application is already throttled (and blocked in for instance its swapBuffer() call).

Then add in any latency added by the system compositor which may or may not be another few vsyncs worth of latency.

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

qputenv("QSG_RENDER_LOOP", "basic");

在 Qt Application 对象实例化之前。

关于c++ - QQuickPaintedItem 使用 QPainter 缓慢更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61795307/

相关文章:

c++ - std set_union 是否总是从第一个开始获取公共(public)元素

c# - 创建与 Windows 服务通信的非托管 DLL(在 C++ 中)?

c++ - 如何在 QML 应用程序中安装和使用用 Qt C++ 编写的事件过滤器

qt - 像设计师一样从应用程序编辑 QML 属性

qt - 从 C++ 向 QML Video Player 输入视频数据

c++ - 声明指针;星号位于类型和名称之间的空格左侧还是右侧?

c++ - 将 SDL 用于 Web 应用程序

c++ - QVariant 无法区分 QDateTime 和 QString

image - Qml Image fillMode需要抗锯齿

c++ - Python 的 "in"运算符的 C++ 等价物是什么?