所以我一直在玩弄QQuickPaintedItem
arpund来制作一个简单的涂鸦应用程序。 从这个平板电脑示例中汲取灵感,我创建了一个基于QQuickPaintedItem
的简单 QML 项目,该项目获取鼠标事件并根据收到的输入在屏幕上绘制路径。然而,在测试中,我意识到我的实现很慢,更具体地说,当鼠标在场景中移动时,油漆滞后于移动。我必须使用自定义QWidget
(使用相同的技术(创建相同的示例,结果要好得多,绘画几乎没有滞后。 我已经记录了这个问题(在 0.5 倍时减慢(:视频 QQuickPaintedItem vs 视频 QWidget。
以下是QQuickPaintedItem
实现的代码:
绘图画布.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
绘图画布.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
绘图小部件.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中的新形状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 绘图同步到屏幕的垂直刷新。这里描述了这个问题的解决方案,其中提到:
为了最大程度地减少延迟,最好的办法是:
使用
QSurfaceFormat::setSwapInterval(0)
(从 5.3 开始(或在系统的控制面板中禁用 vSync。使用 QSG_RENDER_LOOP=basic 运行应用程序以关闭线程/窗口呈现循环。窗口/线程渲染循环将 依靠 vsync 进行限制,因此如果您不设置"基本"动画 将以 100% CPU 的速度旋转。
Qt的鼠标输入处理是通过向GUI线程发布一个偶数来完成的 当鼠标/触摸事件进入时。这将出现在下一个 渲染帧。使用vsync和双/三重缓冲,这意味着 帧在事件发生后 0-33 毫秒进入屏幕。最多到另一个 如果应用程序已受到限制(并且 例如,阻止了它的 swapBuffer(( 调用(。
然后添加系统合成器添加的任何延迟,这可能或 可能不是另外几个 vsync 值的延迟。
对于第 1 步,可以在加载 QML 引擎之前使用非常方便的 QSurfaceFormat::setDefaultFormat。 第 2 步非常重要,因为如果渲染循环未设置为basic
,QML 动画系统在 CPU 上会变得疯狂,并且动画变得太快。要在Qt中设置环境变量,只需调用:
qputenv("QSG_RENDER_LOOP", "basic");
在实例化 Qt 应用程序对象之前。