PDA

View Full Version : Multithreading in QGraphicsItem painting



karankumar1609
23rd July 2013, 07:26
Hello Everyone,

I am working on a project which uses Qt GraphicsScene API. Well i have an item in which i have to print a number of points say(100000 points) which are stored in a std::vector.

My program got some lag when it traverse and paint 100000 points., well i have done some logic that painter should draw the points which are visible on the screen.

But still the program is not that much smooth. i have tried to work with QThread, and moveToThread but unable to solve the problem.

Anyone have idea about how to paint a graphics item in a thread., or something new idea which will resolve my problem and make my program smooth.

QGraphicsView optimizations are already done by my side.



setViewportUpdateMode(QGraphicsView::BoundingRectV iewportUpdate);
setTransformationAnchor(QGraphicsView::AnchorUnder Mouse);
setCacheMode(QGraphicsView::CacheNone);

setOptimizationFlags(QGraphicsView::DontAdjustForA ntialiasing
| QGraphicsView::DontClipPainter
| QGraphicsView::IndirectPainting);

wysota
23rd July 2013, 08:37
My program got some lag when it traverse and paint 100000 points., well i have done some logic that painter should draw the points which are visible on the screen.
This means you're not using the API correctly.


But still the program is not that much smooth. i have tried to work with QThread, and moveToThread but unable to solve the problem.
You cannot use threads for painting in graphics view.

Please show your code related to handling your points.

karankumar1609
23rd July 2013, 08:51
Hei wysota,

Below is the paint function of my plot QGraphicsItem



void LinePlotter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED (widget)
Q_UNUSED (option)

if(!isVisible()) return;

painter->setPen(QPen(Qt::white, .7, Qt::SolidLine, Qt::RoundCap, Qt::MiterJoin));

QRectF visibleRectL = visibleRect();

// a pixmap is created on which all the points will draw
// visibleRectL is the rect of the visible area(Means whole graph should not be painted only concentrate on visible part)
QScopedPointer<QPixmap> pix(new QPixmap(visibleRectL.width(), visibleRectL.height()));
QPainter paint;
paint.begin(pix.data());
paint.setBrush(QBrush(Qt::black));
paint.drawRect(0, 0, visibleRectL.width(), visibleRectL.height());
paint.end();

qreal horizontalPP = horizontalPaddingPixel();
qreal verticalPP = verticalPaddingPixel();

qreal zoomFactorInverse = 1 / zoomFactor();
qreal frontL = front();

QVector<PlotData*> plotDataVector = plotData();

qreal gridY0 = 0, gridY1 = visibleRectL.height();

foreach (PlotData* plotDataValue, plotDataVector) {

KAxisRange yRange = plotDataValue->yRange();

double height = (this->boundingRect().height() - (2* verticalPP));

int startPoint = (frontL * zoomFactorInverse) - (horizontalPP * zoomFactorInverse);

if(startPoint < 0)
startPoint = 0;

int endPoint = startPoint + (visibleRectL.width() * zoomFactorInverse) + 5;

if(endPoint > plotDataValue->totalPixel())
endPoint = plotDataValue->totalPixel();

qreal j = 0;

double xFactor = zoomFactor();
double yFactor = 1.f / (double(yRange.max() - yRange.min()) / height);

std::vector<Plot_Aggregation_Element> plotAggregationElement = plotDataValue->plotData();

// Each vector contains the data of one day
std::vector<Date_Element> dateElement = plotAggregationElement.at(0).ID[0].ID_vector;

foreach (Date_Element dateData, dateElement) {
std::vector<Time_Element> timeElement = dateData.Time_Vector;
QPolygonF LineToDraw;
QVector<QLineF> gridLines;

// each time element contains 330 points and each data element have 30-60 minimum time element
// that means we have atleast 330x60 points in one array and we will have 2-10 arrays
foreach (Time_Element timeData, timeElement) {

if(j >= startPoint &&
j <= endPoint) {
qreal x = j;
x = (x * xFactor) + horizontalPP - ::qCeil(frontL);

// value contains some data in it
if(timeData.Value.max_size() > 0) {
qreal data = timeData.Value[0];
qreal y = data - yRange.min();

y = height - (y * yFactor) + verticalPP;

LineToDraw << QPointF(x, y);
}

{
// Draw Grid
if(QTime::fromString(QString::number(timeData.Time ), "hhmm").minute() == 0)
gridLines << QLineF(QPointF(x, gridY0), QPointF(x, gridY1));
}
} else if(j > endPoint) {
break;
}
j++;
}

painter->setClipRect(0, 0, visibleRect().width(), visibleRect().height());

if(!LineToDraw.isEmpty()) {
/// Draw graph on pixmap
QPainter paint(pix.data());
paint.setPen(plotDataValue->plotColor());
paint.drawPolyline(LineToDraw);

paint.setPen(QPen(QBrush(Qt::red), 1));
paint.setOpacity(0.25);

paint.drawLines(gridLines);
paint.end();
/// Drawing finished
///
}
}
}

/// Draw pixmap with graph
painter->drawPixmap(0, 0, *pix.data());


}


this is the code i need to simplify that code.
the main point is why i am not able to simplity that code coz i need to draw all the things immediately.

Any idea please help

when i draw a single graph it takes 20-30 millisecond which is pretty good., but when i have to draw 6-8 graphs it takes 30x8 = 150 - 240 millisecond, :( i dont like this much delay...

wysota
23rd July 2013, 08:57
Graphics View is an object oriented architecture. It performs optimizations and takes decisions on what to draw based on whether a particular item is visible on the screen or not. If you're not using the object oriented nature of the framework but instead you draw everything yourself using low-level QPainter calls then you're not giving the framework any chance to show its strength. Divide your plot into a series of separate items (separate items for the grid/axis, separate items for each of the points, etc.) and you'll immediately see the difference in performance. If you don't want to do that then you can stuff your code into a simple QWidget instead of QGraphicsView/QGraphicsScene and have similar if not better performance to what you have now.

ChrisW67
23rd July 2013, 09:17
Other things to consider:

Only plot data that is significant. In the limited area of a screen widget it's unlikely that all 100000 points map to different pixels or carry useful information.
Don't draw lines between points if the points are densely packed anyway.
Everything in the inner loop should be fast or moved out of the loop. The string and date manipulations at line 83 certainly seem to be needlessly wasteful: converting int (230) to string ("230"), to a time (02:30), taking the integer minutes (30) and comparing to 0. Seems the same as (230 % 100 == 0) to me.
Use a plotting library like Qwt where much of this work is already done.


Threads do not reduce the amount of work to do.

wysota
23rd July 2013, 09:24
And to add to that -- painting everything to a pixmap that is created and destroyed in the paint routine and then painting that pixmap to the target device is slower then just painting to that device directly. Using clipping yields an additional performance hit as every paint operation has to check against the clipping rect/region.

karankumar1609
23rd July 2013, 10:04
Thanks for all the precious comments., I have converted my paint from painting in pixmap to native painting.
The difference is sharpness.
when i use QPixmap it looks pretty sharp but still the time to paint on the screen is same for both native painting or pixmap painting.

well i have to do that mannually coz size of data and points will be changed at the run time according to slider position.
I have to do something smarter.,

wysota
23rd July 2013, 10:26
well i have to do that mannually coz size of data and points will be changed at the run time according to slider position.
When the number of point changes, just create or destroy items representing those points.

karankumar1609
23rd July 2013, 10:33
What if my points are in the range of 1000 - 6000 and my QGraphicsItem bounding rect is 300x200, how does it scale itself accordingly.
i cant scale my whole QGraphicsView to do that., just because if i scale my view i have to scale everything in the QGraphicsScene.

And ya i have use QGraphicsGridLayout to set plotter item.
There is something which i messed, just because i have jumped to my start point from where to start calculation like we have 100000 points and we have to start calculation from 500 to 550 now my pointer jumps to 500 directly and do calculation till 550 .
But doing this not return me a good result.

i am trying to find out the way to optimize my calculation to make it sharper.

wysota
23rd July 2013, 11:17
What if my points are in the range of 1000 - 6000 and my QGraphicsItem bounding rect is 300x200, how does it scale itself accordingly.
Bounding rect of your item should be the size of the item (thus the size of the point) and should not be related to item position.

Here is a code sample that handles 100k items in the scene quite efficiently (use the mouse wheel to zoom).


#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QWheelEvent>
#include <QStyleOptionGraphicsItem>

class PointItem : public QGraphicsEllipseItem {
public:
PointItem(QGraphicsItem *parent = 0) : QGraphicsEllipseItem(parent) {
setPointSize(2);
setBrush(Qt::red);
setPen(Qt::NoPen);
setFlag(QGraphicsItem::ItemIgnoresTransformations) ;
setCacheMode(QGraphicsItem::DeviceCoordinateCache) ;
}
void setPointSize(qreal s) {
setRect(-s, -s, 2*s, 2*s);
}
};

class GraphicsView : public QGraphicsView {
public:
GraphicsView(QWidget *parent = 0) : QGraphicsView(parent) {}
protected:
void wheelEvent(QWheelEvent *event) {
if(event->delta() > 0) {
scale(1.25, 1.25);
} else {
scale( 0.8, 0.8);
}
}
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GraphicsView view;
QGraphicsScene scene(0, 0, 10000, 5000);
for(int i=0;i<100000;++i) {
PointItem *item = new PointItem;
item->setPos(qrand() % 10000, qrand() % 5000);
scene.addItem(item);
}
view.setScene(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.show();
return a.exec();
}

karankumar1609
23rd July 2013, 12:15
Hello wysota,

your example is the pretty simple one.
But i am working it in a different way.

What your example does:
1. Create a View and Scene of a size
2. plot points(Items) on the scene
3. Scale the scene accordingly on whele event

but something i am working on is different
1. Create a view and scene of a size
2. create axis and plot area and set them in the layout (Layout for multiple graph stacked in a scene)
3. read all the data points and initialize to the plot item
4. On every update plot item draw each graph in it

The different is i can't use scale for zoomIn and ZoomOut purpose coz i have an item of fixed size., only the drawing will vary
means item will virtually zoom in and zoom out horizontally via drawing not via scale., coz if i scale my scene each graph and axis will scale instead of the graph item
and also i have to do something custom with the graph so i did my own painting.

well when i complete zoomOut your code nt remain smooth., anyway thanks for your code.
I think there is some issue with the pixmap painting or the calculation.
may be it should need optimization.

wysota
23rd July 2013, 12:44
Then why are you using QGraphicsView at all?

karankumar1609
23rd July 2013, 13:15
i cant scale my whole QGraphicsView to do that., just because if i scale my view i have to scale everything in the QGraphicsScene.
sorry for missunderstanding.,
but i said that i dont want to scale my view.
the problem with the scale is that i have to scale my whole scene .

wysota
23rd July 2013, 13:24
Please answer my question.

karankumar1609
23rd July 2013, 13:41
well graphicsView is the best API i have seen for drawing purpose.
It automatically update its child items which i dont want to handle.
QWidget need to update manually on each small changes.. that will effect the performance.

On Widget i have to mannually handle and draw each element.
like if i have to add a dynamic line element on graph i have to draw it and handle each and every moment.
In QGraphicsScene its easy.

wysota
23rd July 2013, 14:56
well graphicsView is the best API i have seen for drawing purpose.
It automatically update its child items which i dont want to handle.
You only have one item currently.


QWidget need to update manually on each small changes.. that will effect the performance.
Based on what you have now, changing location of one point in the plot requires all 100k points to be redrawn. That sounds to me like "each small changes requires manual update of the whole plot".


On Widget i have to mannually handle and draw each element.
That's what you do now too. Both QWidget and QGraphicsView use QPainter API for painting. Each time a piece of your data changes, you redraw the whole canvas which is exactly the situation with widgets. With graphics view, modifying one point should only redraw that one point (and possibly others that intersect with it).


like if i have to add a dynamic line element on graph i have to draw it and handle each and every moment.
That's what you do now.


In QGraphicsScene its easy.
Yes, I know. Unfortunately you're using QGraphicsView like it were a plain widget.

karankumar1609
23rd July 2013, 15:08
ya i did the same with QGraphicsItem like with QWidget.

Firstly i started my project with different idea, which suits with QGraphicsView.

nyway thanks for your help. i will do it in QWidget later.
well is QWidget is faster than QGraphicsView ?

wysota
23rd July 2013, 15:58
If you use Graphics View incorrectly then QWidget is likely a bit faster since you don't have the overhead of setting up the environment which you are not using anyway. If you use Graphics View correctly then it will likely be much faster than QWidget, especially for use-cases such as yours where you have a lot of small items and a subset of them is changing in time.

karankumar1609
24th July 2013, 05:01
Thanks wysota,

I have got the problem.
Well the problem is in the two statements in painting.



std::vector<Plot_Aggregation_Element> plotAggregationElement = plotDataValue->plotData();

// Each vector contains the data of one day
std::vector<Date_Element> dateElement = plotAggregationElement.at(0).ID[0].ID_vector;


The above code takes 6-8 millisecond and if there is 8 graph then it takes 6x8 = 48ms.
Now i have to solve this ., and after that it will be faster than before (60 - 48 = 12ms , thats look great).
;)

wysota
24th July 2013, 07:13
Don't copy all the data -- in the code you quoted you are possibly making two copies of a vector (x8, etc.).

karankumar1609
24th July 2013, 07:48
Hello wysota,

What is the better option to use data without copying it :( .

wysota
24th July 2013, 08:59
Return a const reference instead of a copy.

karankumar1609
24th July 2013, 09:45
YAY const reference kills my time., now my plotting time is just not 2-3 ms its 0 ms than 6-8 ms.. ;) :D :rolleyes:
Thanks wysota for your usefull post..