PDA

View Full Version : What is the fastest way to draw a circle ?



Vladimir
4th September 2007, 12:36
Hello All !

I'm using QGraphicsView to visualize a huge amount of particles which are basically circles with fixed size (about 14x14px). The particles can have non-integer coordinates so I'm using antialiasing (without it the scene looks just horrible). When the number of particles is around 200 performance becomes very poor. I've tried drawing using two different methods:


painter->setPen(Qt::NoPen);
painter->setBrush(Qt::black);
painter->drawEllipse(QRectF(-radius,-radius,radius*2,radius*2));

and


painter->setPen(QPen(Qt::black, radius));
painter->drawPoint(0,0);

Both gives approx. the same performance. I've also tried drawing in-advance into QPixmap, but then I can't place that pixmap at non-integer position and there is very noticeable jitter when particle moves.

Are there any suggestions on how to improve drawing performance ? Thanks in advance !

wysota
5th September 2007, 17:31
Could you provide a minimal compilable example reproducing the problem?

Vladimir
6th September 2007, 08:04
Yes, I've prepared an example. Running it on my (not very old) PC I'm getting around 10 FPS. Are there any suggestions how to improve performance ?



#include <QtGui>
#include <QtCore>

const int N = 1000;
const int S = 500;
const int DT = 10;
const double R = 7;

class MyGraphicsItem: public QGraphicsItem
{
public:
MyGraphicsItem(): _vel((double(qrand())/RAND_MAX-0.5)*S/10,(double(qrand())/RAND_MAX-0.5)*S/10),
_rect(-R,-R,2*R,2*R), _color(QColor::fromRgba(qrand())) {
setPos(double(qrand())/RAND_MAX*S,double(qrand())/RAND_MAX*S);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(Qt::NoPen);
painter->setBrush(_color);
painter->drawEllipse(_rect);
}
QRectF boundingRect() const { return _rect; }
void step() {
setPos(pos() + _vel*double(DT)/1000);
if(pos().x() < 0 || pos().x() > S) _vel.setX(-_vel.x());
if(pos().y() < 0 || pos().y() > S) _vel.setY(-_vel.y());
}

protected:
QPointF _vel;
QRectF _rect;
QColor _color;
};

class MyGraphicsScene: public QGraphicsScene
{
public:
MyGraphicsScene(): _startTime(QTime::currentTime()), _framesCount(0) {
for(int i=0; i<N; ++i) addItem(new MyGraphicsItem);
startTimer(0);
}

protected:
void timerEvent(QTimerEvent* event) {
foreach(QGraphicsItem* item, items())
static_cast<MyGraphicsItem*>(item)->step();
QApplication::processEvents();
++_framesCount;
if(_framesCount == 50) {
qDebug() << "FPS:" << double(_framesCount) / _startTime.msecsTo(QTime::currentTime()) * 1000;
_startTime = QTime::currentTime();
_framesCount = 0;
}
}
QTime _startTime;
int _framesCount;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MyGraphicsScene scene;
QGraphicsView view(&scene);
view.setOptimizationFlags(QGraphicsView::DontClipP ainter);
view.setViewportUpdateMode(QGraphicsView::SmartVie wportUpdate);
view.resize(S,S);
view.scale(0.95,0.95);

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

wysota
6th September 2007, 08:37
The first thing I would try is to use OpenGL viewport instead of a regular one (call setViewport(new QGLWidget)) on the view).

Vladimir
6th September 2007, 08:59
With OpenGL I'm getting 17 fps but with worse rendering quality.

Gopala Krishna
6th September 2007, 09:07
Removing QApplication:: processEvents improved FPS by 10 on mys system. I don't think you need to call that since you are not using an infinite loop.

wysota
6th September 2007, 09:12
Try passing QGLFormat(QGL::SampleBuffers) as the first argument to the GLWidget constructor. As far as I remember this should allow antialiasing to work. Also get rid of the scene scaling and change the timer interval from 0 to 50 and see if you can achieve 20fps with it. I seem to have worse hardware than you but I managed to go up to 15fps with it. You could also use the pixel buffer of your graphics card to render the circles. Hopefully it would be faster this way, but you'd probably have to reimplement your paint method to use GL calls (I'm not sure of that though). See the pixel buffer example for details. Maybe you could use a single texture that contains a cirle and just render it differently.

Another thing to try is to render each circle into a pixmap and use drawPixmap() to render the item instead of drawing the ellipse each time.

Vladimir
6th September 2007, 09:48
Removing QApplication:: processEvents improved FPS by 10 on mys system. I don't think you need to call that since you are not using an infinite loop.
Yes, removing it improves performance, but for some strange reason without it MyGraphicsItem::paint() is called only once per two frames !

Gopala Krishna
6th September 2007, 10:07
I guess Wysota is correct in recommending item caching in pixmap. Actually i tried andreas's new item caching patch (http://labs.trolltech.com/blogs/2007/09/05/thoughts-about-graphics-performance/) with your program and with some modifications to your program i could get ~30 FPS. With processEvents i could get ~20FPS.

Here are the changes i made

Firstly i applied the patch specified
Changed update mode to FullViewportMode
Removed scaling
Enabled item's dynamic cache(available only through patch)



#include <QtGui>
#include <QtCore>

const int N = 1000;
const int S = 500;
const int DT = 10;
const double R = 7;

class MyGraphicsItem: public QGraphicsItem
{
public:
MyGraphicsItem(): _vel((double(qrand())/RAND_MAX-0.5)*S/10,(double(qrand())/RAND_MAX-0.5)*S/10),
_rect(-R,-R,2*R,2*R), _color(QColor::fromRgba(qrand())) {

setPos(double(qrand())/RAND_MAX*S,double(qrand())/RAND_MAX*S);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(Qt::NoPen);
painter->setBrush(_color);
painter->drawEllipse(_rect);
}
QRectF boundingRect() const { return _rect; }
void step() {
setPos(pos() + (_vel*double(DT)/1000));
if(pos().x() < 0 || pos().x() > S) _vel.setX(-_vel.x());
if(pos().y() < 0 || pos().y() > S) _vel.setY(-_vel.y());
}

protected:
QPointF _vel;
QRectF _rect;
QColor _color;
};

class MyGraphicsScene: public QGraphicsScene
{
public:
MyGraphicsScene(): _startTime(QTime::currentTime()), _framesCount(0) {
setItemIndexMethod(NoIndex);
for(int i=0; i<N; ++i) {
MyGraphicsItem *it = new MyGraphicsItem();
addItem(it);
it->setCacheMode(QGraphicsItem::DynamicCache);
}
startTimer(0);
}

protected:
void timerEvent(QTimerEvent* event) {
foreach(QGraphicsItem *item, items()) {
static_cast<MyGraphicsItem*>(item)->step();
}

QApplication::processEvents();
++_framesCount;
if(_framesCount == 50) {
qDebug() << "FPS:" << double(_framesCount) / _startTime.msecsTo(QTime::currentTime()) * 1000;
_startTime = QTime::currentTime();
_framesCount = 0;
}
}
QTime _startTime;
int _framesCount;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MyGraphicsScene scene;
QGraphicsView view(&scene);
view.setOptimizationFlags(QGraphicsView::DontClipP ainter);
view.setViewportUpdateMode(QGraphicsView::FullView portUpdate);
view.resize(S,S);

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


EDIT: Forgot to inform you about my hardware. I have descent hardware with 1 GB ram, 3 GHz processor and an ATI Radeon XPress 200m card.

Vladimir
6th September 2007, 10:15
Try passing QGLFormat(QGL::SampleBuffers) as the first argument to the GLWidget constructor. As far as I remember this should allow antialiasing to work.
Thanks, it really works !


Also get rid of the scene scaling and change the timer interval from 0 to 50 and see if you can achieve 20fps with it. I seem to have worse hardware than you but I managed to go up to 15fps with it. You could also use the pixel buffer of your graphics card to render the circles. Hopefully it would be faster this way, but you'd probably have to reimplement your paint method to use GL calls (I'm not sure of that though). See the pixel buffer example for details. Maybe you could use a single texture that contains a cirle and just render it differently.

Another thing to try is to render each circle into a pixmap and use drawPixmap() to render the item instead of drawing the ellipse each time.
I've tried different combinations of it and here is my results:

using drawPixmap (but this gives jitter!):
without GL, without scaling: 25 fps
without GL, scaling: 4 fps (in all other cases scaling does not changes anything)
with GL: 18 fps

using drawEllipse:
without GL: 11 fps
with GL: 17 fps

using drawPoint and big pen:
without GL, without scaling: 9 fps
without GL, without scaling: 7 fps
with GL, without scaling: 13 fps
with GL, with scaling: 12 fps

using drawRect (which is not what I want):
without GL: 15 fps
with GL: 25 fps

EDIT: My hardware is Athlon X2 3800+, 1GB ram, GeForce FX 5500. The code I've used in this benchmark is in the attached file.

So I'll probably stick with using GL. But what if user has not OpenGL (I've tried to run my program in Xephyr and got just error messages). Are there any ways to check it at runtime ?

Vladimir
6th September 2007, 10:18
I guess Wysota is correct in recommending item caching in pixmap. Actually i tried andreas's new item caching patch (http://labs.trolltech.com/blogs/2007/09/05/thoughts-about-graphics-performance/) with your program and with some modifications to your program i could get ~30 FPS. With processEvents i could get ~20FPS.


Thanks for the advice ! But when using QPixmap I'm getting very noticeable jitter when particle moves because QPixmap can only be drawn at integer position. Are this problem fixed with the patch you mentioned ?

wysota
6th September 2007, 10:43
So I'll probably stick with using GL. But what if user has not OpenGL (I've tried to run my program in Xephyr and got just error messages). Are there any ways to check it at runtime ?

QGLFormat::hasOpenGL()
QGLFormat::sampleBuffers()


Thanks for the advice ! But when using QPixmap I'm getting very noticeable jitter when particle moves because QPixmap can only be drawn at integer position. Are this problem fixed with the patch you mentioned ?

You can speed up pixmap scaling by tweaking transformation modes from smooth to fast. You can improve quality by doing the opposite thing. You'll always have aliasing when using pixmaps, because they are pixel based (using smooth transform mode will reduce the effect). But if you use GL pixel buffers and render the circle to texture, you should avoid any artifacts at all. Should be very simple in your case.

Gopala Krishna
6th September 2007, 10:46
Thanks for the advice ! But when using QPixmap I'm getting very noticeable jitter when particle moves because QPixmap can only be drawn at integer position. Are this problem fixed with the patch you mentioned ?

By jitter you mean shaky movement right ? I did get them when using the caching mechanism. But anyway I think pixmap can be drawn in real positions too -

void QPainter::drawPixmap ( const QRectF & target, const QPixmap & pixmap, const QRectF & source )
I found this prototype in assistant.

With both caching and gl i could get 20 FPS. (your card is better than mine ;) )

Vladimir
6th September 2007, 12:03
I found this prototype in assistant.
I've tried this but it still gives the same result.

Vladimir
6th September 2007, 12:13
QGLFormat::hasOpenGL()
QGLFormat::sampleBuffers()
Thanks !


You can speed up pixmap scaling by tweaking transformation modes from smooth to fast. You can improve quality by doing the opposite thing.
I still can't figure out how to do it. I can draw into QPixmap with antialiasing but the problem is that when I'm calling QPainter::drawPixmap (even without any transformations) the pixmap is always painted starting from some pixel on the screen, for example when position of the item is (15.4,19.7) it will still be drawn at position (15,20). When items are slowly moving it gives the impression that the movement is shaky.

wysota
6th September 2007, 14:13
Well... you can't draw in the middle of a pixel. If you scale up the painter, the precision should increase.

Vladimir
6th September 2007, 14:32
Well... you can't draw in the middle of a pixel. If you scale up the painter, the precision should increase.
I could suppose that QT can resample my pixmap using algorithm similar to one using for resizing...

Anyway, for now I've decided to use OpenGL which should be available in 90% cases with fallback to slow software rendering in other cases. On old machines without OpenGL even QPixmap approach can be too slow so I'll probably provide advances options to draw particles as points instead of circles.

wysota
6th September 2007, 16:49
Here is something to play with. Some parts of the code are commented and uncommenting some of them might change the behavior a bit.

The most important part is the one that makes sure the animation is "lag-undependent" - items will move properly regardless of the number of frames the hardware can handle.

Vladimir
6th September 2007, 17:26
Here is something to play with. Some parts of the code are commented and uncommenting some of them might change the behavior a bit.

The most important part is the one that makes sure the animation is "lag-undependent" - items will move properly regardless of the number of frames the hardware can handle.
Thanks ! GL version gives 25fps for me. In my case position calculation is more complicated: each frame is calculated in separate thread and calculation sometimes can be long. However frame time is adjustable so I'll try to follow your ideas and adjust it when rendering takes more time than calculations.