PDA

View Full Version : How can I get a 30 or 60 frame rate when using QGLWidget? QTimer is not acurate



ricardo
9th August 2009, 22:43
Hi dudes!

I have a little problem. I'm doing a level editor where level can be created and played in real time. Now I'm using a QTimer to update my objects (position and logic) and render them (using updateGL from QGLWidget) when somebody wants to play a level.

But using a QTimer is not going well.

"Windows 98 has 55 millisecond accuracy; other systems that we have tested can handle 1 millisecond intervals."
http://doc.trolltech.com/4.5/timers.html

It seems Windows XP has the same problem, just a 55 ms accuracy. BTW, I use VS2005, WinXP and Qt 4.5 on a good PC

The problem is that 55 ms means 18 frames per second, which is not as good as I thought. For gaming I will require at least 30 FPS (60 FPS even better)

Do you have any suggestion or idea?

Do you believe I should use threads or consider them? I dont know too much about theads, What cautions should I take to avoid problems with my UI? (I mean, shared variables or that)

Thanks a lot for your help and time.

wysota
9th August 2009, 23:10
I don't see where threads would help in your situation... If you want, you may constantly call updateGL() on your widget and you will get maximum possible framerate. But the problem is generally in your system - XP should handle resolutions larger than 55ms fine. The limitation of 98 comes from the design of the system which was obviously changed in later Windows systems. How did you test that your XP also has the 55ms limitation? Did you try the same program on different systems?

ricardo
10th August 2009, 00:23
I don't see where threads would help in your situation... If you want, you may constantly call updateGL() on your widget and you will get maximum possible framerate. But the problem is generally in your system - XP should handle resolutions larger than 55ms fine. The limitation of 98 comes from the design of the system which was obviously changed in later Windows systems. How did you test that your XP also has the 55ms limitation? Did you try the same program on different systems?

Thanks for reply.

I did a test for you.

Using QTimer with 20 ms and a QTime to know when my app started:

m_timer=new QTimer(this);
m_timer->setInterval(20);
connect(m_timer, SIGNAL(timeout()), this, SLOT(TimerFired()));

void CEditor::TimerFired() {
qDebug() << "t=" << m_time.elapsed();
}

I got these results, which obviusly are not expected (it should be 9578, 9598...)
t= 9578
t= 9610
t= 9641
t= 9672
t= 9703
t= 9735
t= 9766
t= 9797
t= 9828
t= 9860
t= 9891
t= 9922
t= 9953
t= 9985
t= 10016
t= 10047
t= 10078
t= 10110
t= 10141
t= 10172
t= 10203
t= 10235

Any idea?
I have a QuadCore 2.4 GHz and an ATI HD3870, so I don't thinks is because my computer.
Do you all know what going on is? Note this time I am not redrawing and updating my game on TimerFired, so it is even more strange.
Maybe it is because I'm using DEBUG mode, I don't know.

Thanks a lot for your time.

franz
10th August 2009, 06:54
What you're running into is a limitation of the (type of) operating system. OSes made for people aren't too strict on timing because there is no need for it.

Also remember two things:

Debug mode generally makes your app slower
Outputting stuff to console makes your app slower

ricardo
10th August 2009, 11:06
What you're running into is a limitation of the (type of) operating system. OSes made for people aren't too strict on timing because there is no need for it.

Also remember two things:

Debug mode generally makes your app slower
Outputting stuff to console makes your app slower


Thanks for reply.

I did that, relese mode and not writing anything on console. I wrote this test code:



void CEditor::TimerFired() {
static int rendered_frames=0;
// draw here whatever
m_current_level->Update(0);
m_gl_drawer->updateGL();
rendered_frames++;

// after 10 seconds
int total_time=10000;
if (m_time.elapsed()>total_time) {
float fps=rendered_frames/(0.001*total_time);
QString m=QString("FPS: %1. rendered frames: %2. Timer interval (ms): %3. Total time (ms): %4").arg(fps).arg(rendered_frames).arg(SIMULATION_UPD ATE_INTERVAL_IN_MS).arg(total_time);
QMessageBox::warning(this, "Warning", m);
m_timer->stop();
}
}


That code will simulate my game for just 10 seconds. 20 ms interval (SIMULATION_UPDATE_INTERVAL_IN_MS) should be 50 frames per sencod. As you can see in this picture, something is wrong.

http://img33.imageshack.us/img33/5081/capturav.png (http://img33.imageshack.us/i/capturav.png/)

So, what do you recommend me? Does anyone know why this happens?

Thanks a lot.

ricardo
11th August 2009, 11:26
Any idea? I really have no idea why I cann`t get enough FPS.
Thanks.

hayati
11th August 2009, 12:44
Off course, Qt signal and slot mechanism is also applied to qtimer's timeout signal.
So you're limited to qapplication's event loop to take care this signal in time. No signal must be handled in time off course.
I experienced 2000 threads and believe me that after a few seconds i hardly get any signals, moreover i had never get timeout signal!
so if you want to make it more accurate, use another thread
and don't use ALARM signal as it's system wide. use "nanosleep" or "select" and give timeout parameter as you wish.

i mean, don't use qtimer if you're doing such kind of processing. Call critical functions with system calls(whatever applies to windows). qtimer is bound to event queue which is not accurate.

so i changed my 2000 threaded code to "select" version then it's running flawlessly now.

Windows system may give you built-in easy to use timer but functions that i mentioned before are for Linux.

And off course these accuracy brings complexity to your application.

wysota
11th August 2009, 13:54
This is all wrong. The problem is not the limitation of timers, the problem is that rendering takes time as well. If you set a timeout of a timer to 100ms, then after 1 second you might get from 0 to 10 timeouts (inclusive) and that's perfectly normal. Instead of having a static timeout, one should save the time of last timeout and after the rendering of the frame is done, correct the timeout for next frame to compensate for system being more or less busy.


void MyClass::renderFrame(){
QTime now;
now.start();
renderMe();
QTimer::singleShot(20-now.elapsed(), this, SLOT(renderFrame())); //should get around 50 FPS
}

This should get more accurate timing (although there is still place for improvement which is left as the task to the reader).

hayati
11th August 2009, 14:40
Unfortunately this is not true!
If system is busy in doing someting you may never get timeout calls at all.
and off course painting is a long operation but what is opengl is for.

i mean there is an extra latency for qeventloop which is explained by trolltech's.


may be this thread is useful for explaining latency.

link is here:

http://www.archivum.info/qt-interest@trolltech.com/2005-03/00179/Re:_Event_loop_latency_-_slow_updates_for_OpenGL_:_QEventLoop

and this link:

http://www.archivum.info/qt-interest@trolltech.com/2005-03/00137/Event_loop_latency_-_slow_updates_for_OpenGL

thanks.

wysota
11th August 2009, 17:04
If system is busy in doing someting you may never get timeout calls at all.
That's exactly what I'm saying. After 10 times the timeout value you may get from 0 to 10 timeouts depending on what is happening in the system.


and off course painting is a long operation but what is opengl is for.
You mean you have only fully OpenGL applications in your system?

The latency of the loop is obvious (it does "something", so it introduces a delay), but it is nothing compared to latency of the whole system. Try running a few "while(1);" applications and at the same time run a simple application with a timer triggering at 10Hz, regardless if you are using OpenGL or not.

Bottom line is - without a real time operating system you may never trust any timers - it is a tool, not a panaceum.

hayati
11th August 2009, 18:21
i think second link of my previous post also discusses same issue. That's why i gave these links from trolls' side.

At the end both side agrees on QTimer is not well suited for that kind of update triggering.

Thanks

wysota
11th August 2009, 21:13
At the end both side agrees on QTimer is not well suited for that kind of update triggering.

It's fine, it just needs to be used properly. Using sleep() or any of its variants wouldn't change a thing.

ricardo
11th August 2009, 23:35
@hayati: Are you telling I should use a thread? USually, common games only use a thread in their main loop.
Useful links, Now I reading them, thanks.
If I use a thread to update and render my game, does this create any multithreaded problem? I mean, shared variables... (I'm not an expert in MT apps)
If I use a thread, can I have issues with keyboard and mouse events?



@wysota: Evidently updating and rendering takes time, but I read this form docs: (constant intervals)
"The QTimer class provides a high-level programming interface for timers. To use it, create a QTimer, connect its timeout() signal to the appropriate slots, and call start(). From then on it will emit the timeout() signal at constant intervals."
Did you try your method? are you sure it works? I will try it tomorrow.

"without a real time operating system you may never trust any timers - it is a tool, not a panaceum."
I don't think I need that accuracy, just about 45 and 55 FPS would be nice to create a good simulation.




What do you all thinks about http://www.libqglviewer.com/?
I didn't use it, but reading some docs I read
http://www.libqglviewer.com/features.htm
"Possible animation of the scene at a given frame rate"

Does anyone use that library?


Thanks a lot for help.

wysota
12th August 2009, 00:41
@wysota: Evidently updating and rendering takes time, but I read this form docs: (constant intervals)
"The QTimer class provides a high-level programming interface for timers. To use it, create a QTimer, connect its timeout() signal to the appropriate slots, and call start(). From then on it will emit the timeout() signal at constant intervals."

"Constant intervals" is an approximation. Timers are not guaranteed to fire under stress on non-realtime operating systems. There's really nothing to discuss.


Did you try your method? are you sure it works?
Why shouldn't it work? It's an adaptive timer which makes the frame rate more stable than using a non-adaptive timer.


"without a real time operating system you may never trust any timers - it is a tool, not a panaceum."
I don't think I need that accuracy, just about 45 and 55 FPS would be nice to create a good simulation.
You can't get even that. There is no guarantee - the kernel might preempty your process at any time for any amount of time (like 10 minutes, if it felt like doing so - of course usually it doesn't). There is just no way you can get a timeout during that period.

You have to stick with "most of the time in normal conditions you will get 20-60ms accuracy" (consider the difference between resolution and stability of the rate). But you can compensate for "lost frames" by using adaptive timers (and even skipping rendering frames to catch up) - in reality redrawing framerate is not important at all (as long as it's over 16fps so that your eye can perceive it as animation - the brain will interpolate properly) - it is important that the logic is updated fluently (to prevent the game from "lagging behind") which is completely enough in your case. You can even adapt the complexity of scene rendering to compensate lags. Remember that computer graphics is mostly about cheating.

And to stress again - using another thread will only make things worse as the system will have to context-switch between threads thus losing more time (for a moment ignoring the fact you can't paint from worker threads in Qt) doing completely nothing from your application's point of view. You can use sleep/msleep/nanosleep in the main thread just as well but it won't change the fact that these functions only guarantee that you will not be woken before the given time elapses, nothing more.

faldzip
12th August 2009, 08:17
I am interested in FPS when you don't use timer - just loop which will render as fast as it can. How many fps can you achieve in that way?

ricardo
12th August 2009, 10:57
"Constant intervals" is an approximation. Timers are not guaranteed to fire under stress on non-realtime operating systems. There's really nothing to discuss.


Why shouldn't it work? It's an adaptive timer which makes the frame rate more stable than using a non-adaptive timer.


You can't get even that. There is no guarantee - the kernel might preempty your process at any time for any amount of time (like 10 minutes, if it felt like doing so - of course usually it doesn't). There is just no way you can get a timeout during that period.

You have to stick with "most of the time in normal conditions you will get 20-60ms accuracy" (consider the difference between resolution and stability of the rate). But you can compensate for "lost frames" by using adaptive timers (and even skipping rendering frames to catch up) - in reality redrawing framerate is not important at all (as long as it's over 16fps so that your eye can perceive it as animation - the brain will interpolate properly) - it is important that the logic is updated fluently (to prevent the game from "lagging behind") which is completely enough in your case. You can even adapt the complexity of scene rendering to compensate lags. Remember that computer graphics is mostly about cheating.

And to stress again - using another thread will only make things worse as the system will have to context-switch between threads thus losing more time (for a moment ignoring the fact you can't paint from worker threads in Qt) doing completely nothing from your application's point of view. You can use sleep/msleep/nanosleep in the main thread just as well but it won't change the fact that these functions only guarantee that you will not be woken before the given time elapses, nothing more.

Thanks, so in your opinion the only solution is to implement an adaptative timer. I will try it right now.

By the way: What happens if render takes more than 20 ms? 20-now.elapsed() would be a negative value. Should I control that case and pass a 0 to 20-now.elapsed()?




@faldżip: I don't understand you very well, what do you mean exactly? Maybe this:



MyQGLWidget::doFPSTest() {
...
// draw 1000 frames or so
while (1000 frames not drawn)
{
this->updateScene(); // rotate, translate, etc.
this->updateGL(); // eventually calls paintGL()
// put your time measurement somewhere in between
...
}
}

faldzip
12th August 2009, 11:42
@faldżip: I don't understand you very well, what do you mean exactly? Maybe this:



MyQGLWidget::doFPSTest() {
...
// draw 1000 frames or so
while (1000 frames not drawn)
{
this->updateScene(); // rotate, translate, etc.
this->updateGL(); // eventually calls paintGL()
// put your time measurement somewhere in between
...
}
}


That's what I meant :] Just to see how many FPS you can achieve when you start render of the frame just after you finish rendering prevoius one - so that's the while() what you wrote.

wysota
12th August 2009, 12:24
Thanks, so in your opinion the only solution is to implement an adaptative timer. I will try it right now.
No, it's not a solution. But it will give better results than a static timeout timer.


By the way: What happens if render takes more than 20 ms? 20-now.elapsed() would be a negative value. Should I control that case and pass a 0 to 20-now.elapsed()?
Either use 0 or immediately calculate the next frame (and skip rendering it).

But first see how many frames per second can your application obtain as faldżip suggested.

ricardo
12th August 2009, 12:40
Hi wysota.
After implementing an adaptative timer as you told to me, I get some good results but also a strange result.


3 Tests (3 times each one). 10 seconds every one:


Code:


void CEditor::TimerFired() {
if (m_time.elapsed()>10000) {
ChangeEditorState(EDITING);
} else {
QTime now;
now.start();
m_current_level->Update(0);
m_gl_drawer->updateGL();
m_rendered_frames++;
int next_shot;
if (SIMULATION_UPDATE_INTERVAL_IN_MS-now.elapsed()<0) {
next_shot=0;
} else {
next_shot=SIMULATION_UPDATE_INTERVAL_IN_MS-now.elapsed();
}
QTimer::singleShot(next_shot, this, SLOT(TimerFired()));
}
}

// Start button
m_time.start();
m_rendered_frames=0;
QTimer::singleShot(0, this, SLOT(TimerFired()));

// When ChangeEditorState(EDITING);
float fps=m_rendered_frames/10.0;
QString m=QString("FPS: %1. rendered frames: %2. Timer interval (ms): %3.").arg(fps).arg(m_rendered_frames).arg(SIMULATION_U PDATE_INTERVAL_IN_MS);
QMessageBox::warning(this, "Warning", m);



Results under the same conditions (3 times each one, 9 tests):
If SIMULATION_UPDATE_INTERVAL_IN_MS=10. FPS: 92.7. Expected 100 FPS.
If SIMULATION_UPDATE_INTERVAL_IN_MS=20. FPS: 32. Expected 50 FPS.
If SIMULATION_UPDATE_INTERVAL_IN_MS=30. FPS: 32. Expected 33.3 FPS.

What is really strange is when SIMULATION_UPDATE_INTERVAL_IN_MS=20. I don't understand why I don't get 50 FPS.
Any idea? Is my adaptative timer implemented properly? Should I use high res windows timer and round its result or QTime is fine?

Thanks a lot, very helpful your help.


EDIT: I realised (SIMULATION_UPDATE_INTERVAL_IN_MS-now.elapsed()<0) never happens, so I'm still more disconcerted about interval=20, and next_shot is usually 20, so my computer updates and render it ultra fast. My conclusion is that Qt is taking a lot of time for it. Does anyone know anything about this? Thanks.

wysota
12th August 2009, 13:04
Does your application do anything apart from performing the animation at the same time?

BTW. I'm afraid updateGL() only schedules a repaint, not performs it immediately, so your calculations are not really that precise. As for the 20ms problem - I have no idea... maybe there is some vblank issue related or something. Or maybe you simply didn't repeat the tests enough number of times.

wysota
12th August 2009, 13:04
Does your application do anything apart from performing the animation at the same time?

BTW. I'm afraid updateGL() only schedules a repaint, not performs it immediately, so your calculations are not really that precise. As for the 20ms problem - I have no idea... maybe there is some vblank issue related or something. Or maybe you simply didn't repeat the tests enough number of times.

ricardo
12th August 2009, 13:42
Does your application do anything apart from performing the animation at the same time?

BTW. I'm afraid updateGL() only schedules a repaint, not performs it immediately, so your calculations are not really that precise. As for the 20ms problem - I have no idea... maybe there is some vblank issue related or something. Or maybe you simply didn't repeat the tests enough number of times.

Nope. Just update my world and render it.

I read on docs:
"If you need to trigger a repaint from places other than paintGL() (a typical example is when using timers to animate scenes), you should call the widget's updateGL() function."
From: http://doc.trolltech.com/4.5/qglwidget.html#paintGL

wysota
12th August 2009, 14:14
I read on docs:
"If you need to trigger a repaint from places other than paintGL() (a typical example is when using timers to animate scenes), you should call the widget's updateGL() function."
From: http://doc.trolltech.com/4.5/qglwidget.html#paintGL

Which still doesn't determine when paintGL() will be called after a call to updateGL(). But I just checked in the code - the redraw is immediate.

ricardo
13th August 2009, 10:00
Hi friends!

I think I found a way. just a WHILE(1) and use qApp->processEvents() to avoid a blocked UI. This code is executed when PLAY is pressed. There are no timers.



// simulate it for 10 seconds
m_rendered_frames=0;
m_time.start();
while (m_time.elapsed()<=10000) {
QTime now;
now.start();
qApp->processEvents();
m_current_level->Update(0);
m_gl_drawer->updateGL();
m_rendered_frames++;
// sleep a while
if (SIMULATION_UPDATE_INTERVAL_IN_MS-now.elapsed()>0) {
QTest::qSleep(SIMULATION_UPDATE_INTERVAL_IN_MS-now.elapsed());
}
}
// info
float fps=m_rendered_frames/10.0;
QString m=QString("FPS: %1. rendered frames: %2. Timer interval (ms): %3.").arg(fps).arg(m_rendered_frames).arg(SIMULATION_U PDATE_INTERVAL_IN_MS);
QMessageBox::warning(this, "Warning", m);


It works properly. What do you think?

wysota
13th August 2009, 10:13
Event processing is sparse, keys might not be responding or might respond with a delay. That's not a good way to do it. But if it suits your needs...