Page 1 of 1
optimize CEGUI::System::injectTimePulse
Posted: Mon Nov 16, 2009 07:33
by Jabberwocky
Hiya,
I'm making an RPG-style game, and it has a *lot* of UI.
I recently upgraded to 0.7.1, and I've been toying around with some UI optimizations. In the process, I discovered my per-tick call to CEGUI::System::getSingleton().injectTimePulse(..) was taking a lot of time. This function recurses through every CEGUI Window (i.e. every widget) that exists in your application, and calls Window::update(..). In my game, this was a significant bottleneck. I think this bottleneck was always around - not caused by the 0.7.1 upgrade.
Even though my game has a lot of UI, the vast majority of it is not visible at any given time. (A common trait of RPG games).
So, this optimization made a huge difference to the frame rate in my game:
Old Code:
(CEGUIWindow.cpp)
Code: Select all
void Window::update(float elapsed)
{
// ...
// update child windows
for (size_t i = 0; i < getChildCount(); ++i)
d_children[i]->update(elapsed);
}
New Code:
Code: Select all
void Window::update(float elapsed)
{
// ...
// update child windows
for (size_t i = 0; i < getChildCount(); ++i)
{
if ( d_children[i]->isVisible( true ) )
{
d_children[i]->update(elapsed);
}
}
}
Note the additional isVisible check.
This doesn't appear to have any bad side-effects in my testing, it just speeds everything up.
Re: optimize CEGUI::System::injectTimePulse
Posted: Mon Nov 16, 2009 09:14
by scriptkid
Hi,
Sounds like a good catch, thanks
Performance issues are sometimes only noticed when talking about large numbers. I will add it to the branch and/or trunk.
Bye.
Re: optimize CEGUI::System::injectTimePulse
Posted: Mon Nov 16, 2009 17:21
by Jabberwocky
Cool, thanks.
I forgot to mention the code change is in CEGUIWindow.cpp. I edited my post above to indicate that too.
Re: optimize CEGUI::System::injectTimePulse
Posted: Mon Nov 16, 2009 19:56
by scriptkid
Great, it's in the 0.7 branch now
Re: optimize CEGUI::System::injectTimePulse
Posted: Mon Nov 16, 2009 20:56
by Jabberwocky
Thanks scriptkid,
That was fast!
Re: optimize CEGUI::System::injectTimePulse
Posted: Tue Nov 17, 2009 00:23
by DtD
Great find on that optimization!
~DtD
Re: optimize CEGUI::System::injectTimePulse
Posted: Tue Nov 17, 2009 02:35
by Jabberwocky
Dang. Found an edge case.
Tooltips need to update themselves while invisible. This optimization breaks tooltips. So the optimization code will need to be a little smarter.
There's a simple approach and complex approach we could take.
The simple approach: Explicitly test for tooltips.
Code: Select all
for (size_t i = 0; i < getChildCount(); ++i)
{
// Note: Tooltips are the only CEGUI widget type that needs to be updated while invisible.
if ( d_children[i]->isVisible( true ) || dynamic_cast<CEGUI::Tooltip*>( d_children[i] ) )
{
d_children[i]->update(elapsed);
}
}
The more complex approach would be to add a new CEGUI::Window property, something like "UpdateWhileInvisible", which could then be set explicitly on a per-widget basis.
Sorry I missed this in my testing.
Re: optimize CEGUI::System::injectTimePulse
Posted: Tue Nov 17, 2009 08:11
by scriptkid
Okay good one. I have a re-think about this myself. i will apply your change first though, because i am not sure how many branch users we have.
Re: optimize CEGUI::System::injectTimePulse
Posted: Tue Nov 17, 2009 19:27
by DtD
Perhaps a new "isActivated" function and an "activated" property would be better. That way the widget can decide if it wants to run when it is invisible or not.
~DtD
Re: optimize CEGUI::System::injectTimePulse
Posted: Wed Dec 02, 2009 10:29
by CrazyEddie
With regards to this, I wonder if we can drill down a bit and see if it's the Window::update call that's causing the hit, or whether it's the event trigger that's causing it (i.e. the event fired in the Window::update call). What I mean by that is a simple function call should not be all that expensive (though admittedly, a simple call made often enough can be), though the overhead of firing an event will be much more substantial due to looking up the name of the event every time for every window - IMO it's likely this that needs the optimisation. If it is the event fire that's the real culprit, until events are optimised in general, we could add a setting to enable / disable that event (either runtime or compile time, or both).
CE.
Re: optimize CEGUI::System::injectTimePulse
Posted: Fri Dec 11, 2009 03:06
by Jabberwocky
I ran a few simple tests, measuring framerate in the same scene using various different forms of this optimization.
Test 1: Using unaltered CEGUI code (no optimization)Frame rate: 94
Test 2: Using the optimization I posted above (with a hack to solve the tooltip problem)Code: Select all
void Window::update(float elapsed)
{
// perform update for 'this' Window
updateSelf(elapsed);
// update underlying RenderingWinodw if needed
if (d_surface && d_surface->isRenderingWindow())
static_cast<RenderingWindow*>(d_surface)->update(elapsed);
UpdateEventArgs e(this,elapsed);
fireEvent(EventWindowUpdated,e,EventNamespace);
for (size_t i = 0; i < getChildCount(); ++i)
{
// Note: Tooltips are the only CEGUI widget type that needs to be updated while invisible.
if ( d_children[i]->isVisible( true ) || dynamic_cast<CEGUI::Tooltip*>( d_children[i] ) )
{
d_children[i]->update(elapsed);
}
}
}
Frame rate: 190
Test 3: using a different optimization to squelch the fireEvent call on invisible windowsCode: Select all
void Window::update(float elapsed)
{
// perform update for 'this' Window
updateSelf(elapsed);
// update underlying RenderingWinodw if needed
if (d_surface && d_surface->isRenderingWindow())
static_cast<RenderingWindow*>(d_surface)->update(elapsed);
// Test optimization: only fire an event if we're visible
if ( isVisible() )
{
UpdateEventArgs e(this,elapsed);
fireEvent(EventWindowUpdated,e,EventNamespace);
}
// update child windows
for (size_t i = 0; i < getChildCount(); ++i)
d_children[i]->update(elapsed);
}
Frame rate: 130
So in my usage of CEGUI (*lots* of non-visible windows), the optimization in the child update loop (test 2) is quite a bit faster.
Re: optimize CEGUI::System::injectTimePulse
Posted: Sat Dec 12, 2009 09:26
by CrazyEddie
Hi,
Thanks for this, it really does help me/us a lot, as well as being quite interesting on more than one level
I will take some time shortly to look into this, and the various possibilities - presently I think we will likely end up with a setting or option to enable / disable this call on a per-window basis, I believe this should give the best and most flexible solution without needing to special case specific window types (i.e. ToolTip). This being said, I'll not add that blindly, rather I'll do some proper testing and additional analyses to (hopefully) get the 'right' solution.
Watch this space...
CE.
Re: optimize CEGUI::System::injectTimePulse
Posted: Sat Dec 12, 2009 18:37
by Jabberwocky
Glad I could help. It's one of the great benefits of having open source code to work with. The per-window option sounds like a good approach.
Re: optimize CEGUI::System::injectTimePulse
Posted: Mon Jan 25, 2010 11:00
by CrazyEddie
Hi!
I have just now committed (in v0-7 branch) the - hopefully - final version of this fix / optimisation. What I have done is added Window::setUpdateMode and Window::getUpdateMode functions, along with an UpdateMode property. These can be set to WUM_ALWAYS, WUM_NEVER, WUM_VISIBLE (or "Always", "Never" and "Visible" for the property), in order to control the conditions under which the Window::update call is made for child content. It is set to WUM_VISIBLE by default, though individual window types (like Tooltip) can override this is code or looknfeel, and users can override the setting for any window however and whenever they see fit.
Thanks again for raising this, and the subsequent analyses of possible solutions.
CE.
Re: optimize CEGUI::System::injectTimePulse
Posted: Tue Jan 26, 2010 23:38
by Jabberwocky
Nice work. Thanks CE!