profile: port picture code to qt-quick

This was very painful, because I had to implement rearranging the
paint order of the QSGNodes. The resulting code appears quite
brittle. Let's see where that brings us.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2023-08-12 22:59:56 +02:00
parent d0c26f42d7
commit ebf9ce6d86
22 changed files with 979 additions and 592 deletions

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include "chartitem.h"
#include "chartitemhelper.h"
#include "chartview.h"
#include "chartitem_private.h"
#include <cmath>
#include <QGraphicsScene>
@ -51,7 +51,7 @@ static int round_up(double f)
}
ChartPixmapItem::ChartPixmapItem(ChartView &v, size_t z, bool dragable) : HideableChartItem(v, z, dragable),
positionDirty(false), textureDirty(false)
scale(1.0), positionDirty(false), textureDirty(false)
{
}
@ -74,9 +74,10 @@ void ChartPixmapItem::setPositionDirty()
void ChartPixmapItem::render()
{
doRearrange();
if (!node) {
createNode(view.w()->createImageNode());
view.addQSGNode(node.get(), zValue);
addNodeToView();
}
updateVisible();
@ -95,6 +96,13 @@ void ChartPixmapItem::render()
}
}
// Scale size and round to integer (because non-integers give strange artifacts for me).
static QSizeF scaleSize(const QSizeF &s, double scale)
{
return { round(s.width() * scale),
round(s.height() * scale) };
}
void ChartPixmapItem::resize(QSizeF size)
{
QSize s_int(round_up(size.width()), round_up(size.height()));
@ -106,7 +114,7 @@ void ChartPixmapItem::resize(QSizeF size)
painter.reset(new QPainter(img.get()));
painter->setRenderHint(QPainter::Antialiasing);
}
rect.setSize(size);
rect.setSize(scaleSize(size, scale));
setPositionDirty(); // position includes the size.
setTextureDirty();
}
@ -117,6 +125,15 @@ void ChartPixmapItem::setPos(QPointF pos)
setPositionDirty();
}
void ChartPixmapItem::setScale(double scaleIn)
{
scale = scaleIn;
if (img) {
rect.setSize(scaleSize(img->size(), scale));
setPositionDirty(); // position includes the size.
}
}
void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &scene)
{
resize(s); // Noop if size doesn't change
@ -238,6 +255,7 @@ void ChartLineItemBase::setLine(QPointF from, QPointF to)
void ChartLineItem::render()
{
doRearrange();
if (!node) {
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
geometry->setDrawingMode(QSGGeometry::DrawLines);
@ -245,7 +263,7 @@ void ChartLineItem::render()
createNode();
node->setGeometry(geometry.get());
node->setMaterial(material.get());
view.addQSGNode(node.get(), zValue);
addNodeToView();
positionDirty = materialDirty = true;
}
updateVisible();
@ -269,6 +287,7 @@ void ChartLineItem::render()
void ChartRectLineItem::render()
{
doRearrange();
if (!node) {
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 5));
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
@ -276,7 +295,7 @@ void ChartRectLineItem::render()
createNode();
node->setGeometry(geometry.get());
node->setMaterial(material.get());
view.addQSGNode(node.get(), zValue);
addNodeToView();
positionDirty = materialDirty = true;
}
updateVisible();

View file

@ -47,11 +47,27 @@ protected:
std::unique_ptr<Node> node;
bool visible;
bool visibleChanged;
enum class MoveMode {
none, before, after
} moveMode; // Node will be moved before or after other node.
QSGNode *moveNode; // Node to be moved before/after, or nullptr if move to beginning/end.
template<class... Args>
void createNode(Args&&... args); // Call to create node with visibility flag.
void updateVisible(); // Must be called by child class to update visibility flag!
void addNodeToView(); // Must be called by child class after creating and initializing the QSG node.
void doRearrange(); // Call at beginning of render(), so that the node can be rearranged, if necessary.
public:
template <typename Node2>
friend class HideableChartItem;
template <typename Node2>
void moveBefore(HideableChartItem<Node2> &item);
void moveBack();
template <typename Node2>
void moveAfter(HideableChartItem<Node2> &item);
void moveFront();
void setVisible(bool visible);
bool isVisible() const;
};
// A shortcut for ChartItems based on a hideable proxy item
@ -59,12 +75,14 @@ template <typename Node>
using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<Node>>>;
// A chart item that blits a precalculated pixmap onto the scene.
// Can be scaled with setScale().
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
public:
ChartPixmapItem(ChartView &v, size_t z, bool dragable = false);
~ChartPixmapItem();
void setPos(QPointF pos) override;
void setScale(double scale);
void render() override;
protected:
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
@ -72,6 +90,7 @@ protected:
std::unique_ptr<QImage> img;
void setTextureDirty();
void setPositionDirty();
double scale;
private:
bool positionDirty; // true if the position changed since last render
bool textureDirty; // true if the pixmap changed since last render
@ -161,6 +180,41 @@ public:
};
// Implementation detail of templates - move to serparate header file
template <typename Node>
template <typename Node2>
void HideableChartItem<Node>::moveBefore(HideableChartItem<Node2> &item)
{
moveMode = MoveMode::before;
moveNode = item.node.get();
markDirty();
}
template <typename Node>
void HideableChartItem<Node>::moveBack()
{
moveMode = MoveMode::before;
moveNode = nullptr;
markDirty();
}
template <typename Node>
template <typename Node2>
void HideableChartItem<Node>::moveAfter(HideableChartItem<Node2> &item)
{
moveMode = MoveMode::after;
moveNode = item.node.get();
markDirty();
}
template <typename Node>
void HideableChartItem<Node>::moveFront()
{
moveMode = MoveMode::after;
moveNode = nullptr;
markDirty();
}
template <typename Node>
void HideableChartItem<Node>::setVisible(bool visibleIn)
{
@ -172,25 +226,9 @@ void HideableChartItem<Node>::setVisible(bool visibleIn)
}
template <typename Node>
template<class... Args>
void HideableChartItem<Node>::createNode(Args&&... args)
bool HideableChartItem<Node>::isVisible() const
{
node.reset(new Node(visible, std::forward<Args>(args)...));
visibleChanged = false;
}
template <typename Node>
HideableChartItem<Node>::HideableChartItem(ChartView &v, size_t z, bool dragable) : ChartItem(v, z, dragable),
visible(true), visibleChanged(false)
{
}
template <typename Node>
void HideableChartItem<Node>::updateVisible()
{
if (visibleChanged)
node->setVisible(visible);
visibleChanged = false;
return visible;
}
#endif

View file

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
// Private template implementation for ChartItem child classes
#ifndef CHARTITEM_PRIVATE_H
#define CHARTITEM_PRIVATE_H
#include "chartitem.h"
#include "chartview.h"
template <typename Node>
template<class... Args>
void HideableChartItem<Node>::createNode(Args&&... args)
{
node.reset(new Node(visible, std::forward<Args>(args)...));
visibleChanged = false;
}
template <typename Node>
void HideableChartItem<Node>::addNodeToView()
{
view.addQSGNode(node.get(), zValue, moveMode == MoveMode::after, moveNode);
moveNode = nullptr;
moveMode = MoveMode::none;
}
template <typename Node>
HideableChartItem<Node>::HideableChartItem(ChartView &v, size_t z, bool dragable) : ChartItem(v, z, dragable),
visible(true), visibleChanged(false), moveMode(MoveMode::none), moveNode(nullptr)
{
}
template <typename Node>
void HideableChartItem<Node>::updateVisible()
{
if (visibleChanged)
node->setVisible(visible);
visibleChanged = false;
}
template <typename Node>
void HideableChartItem<Node>::doRearrange()
{
if (!node)
return;
switch (moveMode) {
default:
case MoveMode::none:
return;
case MoveMode::before:
if (moveNode)
view.moveNodeBefore(node.get(), zValue, moveNode);
else
view.moveNodeBack(node.get(), zValue);
break;
case MoveMode::after:
if (moveNode)
view.moveNodeAfter(node.get(), zValue, moveNode);
else
view.moveNodeFront(node.get(), zValue);
break;
}
moveNode = nullptr;
moveMode = MoveMode::none;
}
#endif

View file

@ -21,6 +21,9 @@ public:
ChartItemPtr(const ChartItemPtr &p) : ptr(p.ptr)
{
}
ChartItemPtr(ChartItemPtr &&p) : ptr(p.ptr)
{
}
void reset()
{
ptr = nullptr;

View file

@ -127,10 +127,54 @@ void ChartView::clearItems()
dirtyItems.splice(deletedItems);
}
void ChartView::addQSGNode(QSGNode *node, size_t z)
static ZNode &getZNode(RootNode &rootNode, size_t z)
{
size_t idx = std::clamp(z, (size_t)0, maxZ);
rootNode->zNodes[idx]->appendChildNode(node);
size_t idx = std::clamp(z, (size_t)0, rootNode.zNodes.size());
return *rootNode.zNodes[idx];
}
void ChartView::addQSGNode(QSGNode *node, size_t z, bool moveAfter, QSGNode *node2)
{
auto &parent = getZNode(*rootNode, z);
if (node2) {
if (moveAfter)
parent.insertChildNodeAfter(node, node2);
else
parent.insertChildNodeBefore(node, node2);
} else {
if (moveAfter)
parent.prependChildNode(node);
else
parent.appendChildNode(node);
}
}
void ChartView::moveNodeBefore(QSGNode *node, size_t z, QSGNode *before)
{
auto &parent = getZNode(*rootNode, z);
parent.removeChildNode(node);
parent.insertChildNodeBefore(node, before);
}
void ChartView::moveNodeBack(QSGNode *node, size_t z)
{
auto &parent = getZNode(*rootNode, z);
parent.removeChildNode(node);
parent.appendChildNode(node);
}
void ChartView::moveNodeAfter(QSGNode *node, size_t z, QSGNode *before)
{
auto &parent = getZNode(*rootNode, z);
parent.removeChildNode(node);
parent.insertChildNodeAfter(node, before);
}
void ChartView::moveNodeFront(QSGNode *node, size_t z)
{
auto &parent = getZNode(*rootNode, z);
parent.removeChildNode(node);
parent.prependChildNode(node);
}
void ChartView::registerChartItem(ChartItem &item)

View file

@ -20,7 +20,13 @@ public:
QSizeF size() const;
QRectF plotArea() const;
void setBackgroundColor(QColor color); // Chart must be replot for color to become effective.
void addQSGNode(QSGNode *node, size_t z); // Must only be called in render thread!
void addQSGNode(QSGNode *node, size_t z, bool moveAfter, QSGNode *node2);
// Must only be called in render thread!
// If node2 is nullptr move to begin or end of list.
void moveNodeBefore(QSGNode *node, size_t z, QSGNode *before);
void moveNodeBack(QSGNode *node, size_t z);
void moveNodeAfter(QSGNode *node, size_t z, QSGNode *after);
void moveNodeFront(QSGNode *node, size_t z);
void registerChartItem(ChartItem &item);
void registerDirtyChartItem(ChartItem &item);
void emergencyShutdown(); // Called when QQuick decides to delete our root node.