使用QPainter缓慢更新QQuickPaintedItem

daljit97

所以我一直在玩arpundQQuickPaintedItem来制作一个简单的涂鸦应用程序。从这个Tablet示例中得到启发,我创建了一个简单的QML项目,基于QQuickPaintedItem项目获取鼠标事件并根据收到的输入在屏幕上绘制路径。但是,通过测试,我意识到我的实现很慢,尤其是当鼠标在场景中移动时,绘画落后于移动。我必须使用自定义QWidget(使用相同的技术)创建相同的示例,并且结果要好得多,几乎没有绘画上的滞后。我已经记录了这个问题(速度降低为0.5倍):Video QQuickPaintedItem vs Video 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)
        }
    }
daljit97

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

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

  1. 使用QSurfaceFormat::setSwapInterval(0)(从5.3开始)或在系统的控制面板中禁用vsync

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

Qt的鼠标输入处理是通过在鼠标/触摸事件发生时在GUI线程上发布一个偶数来完成的。这将在下一个渲染的帧中显示。使用vsync和double / triple缓冲,这意味着事件发生后0-33 ms帧到达屏幕。如果应用程序已经被限制(例如被阻塞在其swapBuffer()调用中),则将增加多达16ms的延迟。

然后添加系统合成器添加的任何延迟,这可能是也可能不是另外几个值得同步的延迟。

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

qputenv("QSG_RENDER_LOOP", "basic");

在实例化Qt Application对象之前。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

使用LIKE在哪里更新缓慢

来自分类Dev

使用带有QQuickPaintedItem :: paint()的drawRect

来自分类Dev

UIPickerView更新缓慢

来自分类Dev

Amazon CloudDistribution缓慢更新

来自分类Dev

MongoDB批量更新缓慢

来自分类Dev

更新后启动缓慢

来自分类Dev

Amazon CloudDistribution缓慢更新

来自分类Dev

使用QPainter平滑绘制

来自分类Dev

使用QAbstractItemModel返回QQuickPaintedItem以在QML委托中使用

来自分类Dev

NetworkStream.DataAvailable更新缓慢

来自分类Dev

领域更新多个对象缓慢

来自分类Dev

“统计”功能的更新缓慢吗?

来自分类Dev

使用QPainter绘制富文本

来自分类Dev

多次使用QPainter和QPaintDevice

来自分类Dev

使用带有块的EF更新数据库运行非常缓慢

来自分类Dev

使用 python3 上的大块 (60MB) 在 SQLITE3 上缓慢更新

来自分类Dev

网站CSS和JavaScript更新缓慢

来自分类Dev

SQLite更新ONE记录非常(相对)缓慢

来自分类Dev

ReactJS缓慢地更新大型DOM?

来自分类Dev

UIScrollView上的渐变蒙版更新缓慢

来自分类Dev

EntityFramework在执行更新查询时非常缓慢

来自分类Dev

Java游戏更新缓慢几秒钟

来自分类Dev

Django MPTT Postgres更新查询运行缓慢

来自分类Dev

上次更新后Ubuntu死机缓慢

来自分类Dev

19.04之后,apt-get更新缓慢

来自分类Dev

window.location更改后更新缓慢

来自分类Dev

为什么更新mysql查询运行缓慢

来自分类Dev

缓慢插入并更新到大表中

来自分类Dev

使用QPainter时对象是透明的