PDA

View Full Version : Improving GV performance



Gopala Krishna
2nd June 2007, 07:56
Hi,
I had asked this kind of question in past but i concentrated more on QGraphicsScene::drawBackground() that time.

Here follows an example of three possible approach of drawing resistor with nodes. To test while running, select all elements of particular colour and move them together and notice difference for different colours. Obviously the last part, drawing nodes as part of component is faster. But this results in lot of book keeping for my app which I think will not be a nice trade off (sacrificing benifits of gv).

I am using the first method, where nodes are moved manually. Originally in my app I move them in scene, but for example sake I'm moving in itemChange().

My questions are:
1) Using 2nd method (NodesAsChild) doubles number of items when the components are connected. But using first method I will just have a single node for connected components where i just add a reference in nodes component list.
Will the performance decrease significantly if I use 2nd method ?

2) Are there any other ways of opimizing any of the methods further ?

3) Are there any other method of doing what I want, I mean design ?

I'm using Qt 4.2.3 on kubuntu.


#include <QtGui>
#include <cmath>

const int NodeRadius = 3;

inline qreal distance(const QPointF& p1, const QPointF& p2)
{
qreal dx = p1.x() - p2.x();
qreal dy = p1.y() - p2.y();
return std::sqrt((dx*dx)+(dy*dy));
}


class Node : public QGraphicsItem
{
public:
enum { Type = UserType + 1 };

Node(QGraphicsItem *parent=0, QGraphicsScene *scene=0) : QGraphicsItem(parent,scene)
{
setAcceptedMouseButtons(0);
setFlags(0);
}

QRectF boundingRect() const
{
return QRectF(-NodeRadius, -NodeRadius, 2*NodeRadius, 2*NodeRadius);
}

void paint(QPainter *p, const QStyleOptionGraphicsItem *o, QWidget *w = 0)
{
Q_UNUSED(o);
Q_UNUSED(w);
p->setRenderHints(QPainter::Antialiasing);
p->setPen(Qt::darkRed);
p->drawEllipse(boundingRect());
}

bool contains(const QPointF& pt) const
{
qreal dist = distance(QPointF(0,0),pt);
return (((dist * dist) - (NodeRadius*NodeRadius)) <= 0);
}

bool collidesWithItem(QGraphicsItem *other) const
{
Node *port = qgraphicsitem_cast<Node*>(other);
if(!port)
return QGraphicsItem::collidesWithItem(other);
qreal dist = distance(pos(),port->pos());

return (dist <= (2 * NodeRadius));
}

QPainterPath shape() const
{
QPainterPath path;
path.addEllipse(boundingRect());
return path;
}

int type() const
{
return Type;
}

};

namespace SeparateNodes
{
class Resistor : public QGraphicsItem
{
public:
Resistor(QGraphicsItem *parent = 0, QGraphicsScene *scene=0) : QGraphicsItem(parent,scene)
{
setFlags(ItemIsMovable | ItemIsSelectable | ItemIsFocusable);
Node *n = new Node(0,scene);
n->setPos(-27.0,0.0);
nodes << n;
n = new Node(0,scene);
n->setPos(27.0,0.0);
nodes << n;
}

QRectF boundingRect() const
{
return QRectF(-27,-9,54,18);
}

void paint(QPainter *p, const QStyleOptionGraphicsItem *o, QWidget *w = 0)
{
Q_UNUSED(o);
Q_UNUSED(w);
p->setPen(Qt::darkMagenta);
p->drawLine(-18, -9, 18, -9);
p->drawLine( 18, -9, 18, 9);
p->drawLine( 18, 9,-18, 9);
p->drawLine(-18, 9,-18, -9);
p->drawLine(-27, 0,-18, 0);
p->drawLine( 18, 0, 27, 0);

if( o->state & QStyle::State_Selected)
{
p->setPen(Qt::darkGray);
p->drawRect(boundingRect());
}

}

QVariant itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
QPointF newPos = value.toPointF();
QPointF delta = newPos - pos();
foreach(Node *n, nodes)
n->moveBy(delta.x(), delta.y());
}
return QGraphicsItem::itemChange(change, value);
}

QList<Node*> nodes;
};
}

namespace NodesAsChild
{
class Resistor : public QGraphicsItem
{
public:
Resistor(QGraphicsItem *parent = 0, QGraphicsScene *scene=0) : QGraphicsItem(parent,scene)
{
setFlags(ItemIsMovable | ItemIsSelectable | ItemIsFocusable);
Node *n = new Node(this,scene);
n->setPos(-27.0,0.0);
n = new Node(this,scene);
n->setPos(27.0,0.0);
}

QRectF boundingRect() const
{
return QRectF(-27,-9,54,18);
}

void paint(QPainter *p, const QStyleOptionGraphicsItem *o, QWidget *w = 0)
{
Q_UNUSED(o);
Q_UNUSED(w);
p->setPen(Qt::darkBlue);
p->drawLine(-18, -9, 18, -9);
p->drawLine( 18, -9, 18, 9);
p->drawLine( 18, 9,-18, 9);
p->drawLine(-18, 9,-18, -9);
p->drawLine(-27, 0,-18, 0);
p->drawLine( 18, 0, 27, 0);

if( o->state & QStyle::State_Selected)
{
p->setPen(Qt::darkGray);
p->drawRect(boundingRect());
}

}
};
}

namespace NodesPartOfResistor
{
class Resistor : public QGraphicsItem
{
public:
Resistor(QGraphicsItem *parent = 0, QGraphicsScene *scene=0) : QGraphicsItem(parent,scene)
{
setFlags(ItemIsMovable | ItemIsSelectable | ItemIsFocusable);
}

QRectF boundingRect() const
{
return QRectF(-30,-12,60,24);
}

void paint(QPainter *p, const QStyleOptionGraphicsItem *o, QWidget *w = 0)
{
Q_UNUSED(o);
Q_UNUSED(w);
p->setPen(Qt::darkGreen);
p->drawLine(-18, -9, 18, -9);
p->drawLine( 18, -9, 18, 9);
p->drawLine( 18, 9,-18, 9);
p->drawLine(-18, 9,-18, -9);
p->drawLine(-27, 0,-18, 0);
p->drawLine( 18, 0, 27, 0);

p->setPen(Qt::darkRed);
p->setRenderHints(QPainter::Antialiasing);
p->drawEllipse(-27-NodeRadius,-NodeRadius,2*NodeRadius,2*NodeRadius);
p->drawEllipse(27-NodeRadius,-NodeRadius,2*NodeRadius,2*NodeRadius);
if( o->state & QStyle::State_Selected)
{
p->setPen(Qt::darkGray);
p->drawRect(boundingRect());
}

}
};
}

int main(int argc, char *argv[])
{
QApplication app(argc,argv);
QGraphicsScene *scene = new QGraphicsScene(0.,0.,1024,768);
QGraphicsView *view = new QGraphicsView(scene);
view->setDragMode(QGraphicsView::RubberBandDrag);
view->setAcceptDrops(true);
//view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
const int factor = 68;
for(int i = 1; i < 11; i++)
for(int j=1;j<3;j++) {
SeparateNodes::Resistor *r = new SeparateNodes::Resistor(0,scene);
r->setPos(100+i*factor,j*100);
}

for(int i = 1; i < 11; i++)
for(int j=1;j<3;j++) {
NodesAsChild::Resistor *r = new NodesAsChild::Resistor(0,scene);
r->setPos(100+i*factor,200+j*100);
}

for(int i = 1; i < 11; i++)
for(int j=1;j<3;j++) {
NodesPartOfResistor::Resistor *r = new NodesPartOfResistor::Resistor(0,scene);
r->setPos(100+i*factor,400+j*100);
}

view->show();
return app.exec();
}

wysota
2nd June 2007, 09:08
Have you seen the elastic nodes example that comes with Qt? It does more or less the same you want.

I'm having trouble here detecting small differences between approaches... Why do you say in the second try the number of nodes will be doubled compared to the first one? You create two new nodes in both cases when adding a resistor.

Gopala Krishna
2nd June 2007, 09:42
Have you seen the elastic nodes example that comes with Qt? It does more or less the same you want.
I've seen that but it uses some animation which i dont need. Anyway both the implementation seems to be similar.



I'm having trouble here detecting small differences between approaches... Why do you say in the second try the number of nodes will be doubled compared to the first one? You create two new nodes in both cases when adding a resistor.

I'm sorry for not being clear enough. In the first approach whenever two components are connected, they share a common node and so i delete one of the nodes. I maintain a list of connected components in node, which i update in such case. This also shows visual change by using a brush with solid fill in the node showing connection.
When a connection happens and I need to move the node(by moving component), I do that in scene's mouse event carefully selecting the appropriate component to control the node movement.

BTW do you think the second approach is slightly faster(though i cant see difference now) when there's no connection since the parent item includes child's boundRect there by resulting in less updates ?

wysota
2nd June 2007, 12:10
I've seen that but it uses some animation which i dont need.

I meant to take a look how the nodes are tied to the "rubbers".


I'm sorry for not being clear enough. In the first approach whenever two components are connected, they share a common node and so i delete one of the nodes.
I don't think this is implemented in the code you provided. I was expecting something like that but couldn't find it.


BTW do you think the second approach is slightly faster(though i cant see difference now) when there's no connection since the parent item includes child's boundRect there by resulting in less updates ?

No, I don't think it makes the whole thing faster. boundingRect of the parent doesn't have to include boundingRects of all its children.

I think you should make the connectors separate objects and keep pointers to them in your items. Then when you move the item, update its connector in a way you see fit - either by moving only the ending point of the connector or by iterating over all its points and moving each next one sliightly less than the previous one (provided that your connectors have more than two points, of course).

Gopala Krishna
2nd June 2007, 19:24
Thanks for replying :)



I don't think this is implemented in the code you provided. I was expecting something like that but couldn't find it.

It is quite difficult to compress that code into a small example, so I didn't do it :)



No, I don't think it makes the whole thing faster. boundingRect of the parent doesn't have to include boundingRects of all its children.

Thanks for this information. I wont try 2nd method in my original app. Probably the third approach can be tried , but I'm afraid if the performance might not improve significantly (due to lot of bookkeeping)




I think you should make the connectors separate objects and keep pointers to them in your items. Then when you move the item, update its connector in a way you see fit - either by moving only the ending point of the connector or by iterating over all its points and moving each next one sliightly less than the previous one (provided that your connectors have more than two points, of course).
Can you elaborate more ? I didn't get this clearly ? What do you mean by connector - node or a wire ?

wysota
2nd June 2007, 20:09
Wire, of course.

Gopala Krishna
3rd June 2007, 07:02
Well , actually i dont have problem with wire for now. What exactly I do is, if a node has more than 2 items(component or wire), i fill the node with solid brush. If the components are moved apart I add a wire between.

I installed opensource version of qt-4.3 for windows and the result didn't differ much :(

I made an important observation though:
Select all the components and move them all. Notice the speed of moving components.
Now set the dactor variable in my snippet to 54

const int factor = 54;
Doing so will overlap the nodes of elements in a row.
Now see the difference!

With this what I inferred was, there are less number of updates(with bigger update region) and hence faster.
Can I use some tricks to fake situation like this so that when components are moved ?

I went through the chip demo, but it doesn't use child items and also the items are rectangular. Hence i can't directly compare chip and my code. Still performance seems to be better in chip. I welcome any suggestion and correction if i am wrong.
I seriosly want to improve the performance of my app. A client doesn't care what technology you use, he just wants good result. So please do help me. Should I fallback to third approach ? :eek:

Gopala Krishna
3rd June 2007, 07:59
After I read this post #69 (http://www.qtcentre.org/forum/f-qt-programming-2/t-graphicsview-performance-problems-page4-5684.html), I tried this

view->setViewportUpdateMode(QGraphicsView::SmartViewport Update);
and speed improvements are amazing :p. may be I can use this when items are moving and switch back to default in other cases.

Never the less, i still want answer for my questions above since the app has to be compatible with qt-4.2 :(