Line data Source code
1 :
2 : /* Copyright (c) 2008-2013, Stefan Eilemann <eile@equalizergraphics.com>
3 : *
4 : * This library is free software; you can redistribute it and/or modify it under
5 : * the terms of the GNU Lesser General Public License version 2.1 as published
6 : * by the Free Software Foundation.
7 : *
8 : * This library is distributed in the hope that it will be useful, but WITHOUT
9 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
11 : * details.
12 : *
13 : * You should have received a copy of the GNU Lesser General Public License
14 : * along with this library; if not, write to the Free Software Foundation, Inc.,
15 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 : */
17 :
18 : #include "framerateEqualizer.h"
19 :
20 : #include "../compound.h"
21 : #include "../compoundVisitor.h"
22 : #include "../config.h"
23 : #include "../log.h"
24 :
25 : #include <eq/client/statistic.h>
26 : #include <lunchbox/debug.h>
27 :
28 : #define USE_AVERAGE
29 : #define VSYNC_CAP 60.f
30 : #define SLOWDOWN 1.05f
31 :
32 : namespace eq
33 : {
34 : namespace server
35 : {
36 :
37 : namespace
38 : {
39 0 : class LoadSubscriber : public CompoundVisitor
40 : {
41 : public:
42 0 : LoadSubscriber( ChannelListener* listener ) : _listener( listener ) {}
43 :
44 0 : virtual VisitorResult visit( Compound* compound )
45 : {
46 0 : Channel* channel = compound->getChannel();
47 0 : LBASSERT( channel );
48 0 : channel->addListener( _listener );
49 :
50 0 : return TRAVERSE_CONTINUE;
51 : }
52 :
53 : private:
54 : ChannelListener* const _listener;
55 : };
56 :
57 0 : class LoadUnsubscriber : public CompoundVisitor
58 : {
59 : public:
60 0 : LoadUnsubscriber( ChannelListener* listener ) : _listener( listener ) {}
61 :
62 0 : virtual VisitorResult visit( Compound* compound )
63 : {
64 0 : Channel* channel = compound->getChannel();
65 0 : LBASSERT( channel );
66 0 : channel->removeListener( _listener );
67 :
68 0 : return TRAVERSE_CONTINUE;
69 : }
70 :
71 : private:
72 : ChannelListener* const _listener;
73 : };
74 :
75 : }
76 :
77 : // The smooth load balancer adapts the framerate of the compound to be the
78 : // average frame rate of all children, taking the DPlex period into account.
79 :
80 14 : FramerateEqualizer::FramerateEqualizer()
81 14 : : _nSamples( 0 )
82 : {
83 14 : LBINFO << "New FramerateEqualizer @" << (void*)this << std::endl;
84 14 : }
85 :
86 0 : FramerateEqualizer::FramerateEqualizer( const FramerateEqualizer& from )
87 : : Equalizer( from )
88 0 : , _nSamples( 0 )
89 : {
90 0 : }
91 :
92 42 : FramerateEqualizer::~FramerateEqualizer()
93 : {
94 14 : attach( 0 );
95 28 : }
96 :
97 42 : void FramerateEqualizer::attach( Compound* compound )
98 : {
99 42 : _exit();
100 42 : Equalizer::attach( compound );
101 42 : }
102 :
103 :
104 0 : void FramerateEqualizer::_init()
105 : {
106 0 : const Compound* compound = getCompound();
107 :
108 0 : if( _nSamples > 0 || !compound )
109 0 : return;
110 :
111 0 : _nSamples = 1;
112 :
113 : // Subscribe to child channel load events
114 0 : const Compounds& children = compound->getChildren();
115 :
116 0 : LBASSERT( _loadListeners.empty( ));
117 0 : _loadListeners.resize( children.size( ));
118 :
119 0 : for( size_t i = 0; i < children.size(); ++i )
120 : {
121 0 : Compound* child = children[i];
122 0 : const uint32_t period = child->getInheritPeriod();
123 0 : LoadListener& loadListener = _loadListeners[i];
124 :
125 0 : loadListener.parent = this;
126 0 : loadListener.period = period;
127 :
128 0 : LoadSubscriber subscriber( &loadListener );
129 0 : child->accept( subscriber );
130 :
131 0 : _nSamples = LB_MAX( _nSamples, period );
132 0 : }
133 :
134 0 : _nSamples = LB_MIN( _nSamples, 100 );
135 : }
136 :
137 42 : void FramerateEqualizer::_exit()
138 : {
139 42 : const Compound* compound = getCompound();
140 42 : if( !compound || _nSamples == 0 )
141 84 : return;
142 :
143 0 : const Compounds& children = compound->getChildren();
144 :
145 0 : LBASSERT( _loadListeners.size() == children.size( ));
146 0 : for( size_t i = 0; i < children.size(); ++i )
147 : {
148 0 : Compound* child = children[i];
149 0 : LoadListener& loadListener = _loadListeners[i];
150 :
151 0 : LoadUnsubscriber unsubscriber( &loadListener );
152 0 : child->accept( unsubscriber );
153 0 : }
154 :
155 0 : _loadListeners.clear();
156 0 : _times.clear();
157 0 : _nSamples = 0;
158 : }
159 :
160 :
161 0 : void FramerateEqualizer::notifyUpdatePre( Compound* compound,
162 : const uint32_t frameNumber )
163 : {
164 0 : _init();
165 :
166 : // find starting point of contiguous block
167 0 : const ssize_t size = ssize_t( _times.size( ));
168 0 : ssize_t from = 0;
169 0 : if( size > 0 )
170 : {
171 0 : for( ssize_t i = size-1; i >= 0; --i )
172 : {
173 0 : if( _times[i].second == 0.f )
174 : {
175 0 : from = i;
176 0 : break;
177 : }
178 : }
179 : }
180 :
181 : // find max / avg time in block
182 0 : size_t nSamples = 0;
183 : #ifdef USE_AVERAGE
184 0 : float sumTime = 0.f;
185 : #else
186 : float maxTime = 0.f;
187 : #endif
188 :
189 0 : LBLOG( LOG_LB2 ) << "Searching " << from+1 << ".." << size << std::endl;
190 0 : for( ++from; from < size && nSamples < _nSamples; ++from )
191 : {
192 0 : const FrameTime& time = _times[from];
193 0 : LBASSERT( time.first > 0 );
194 0 : LBASSERT( time.second != 0.f );
195 :
196 0 : ++nSamples;
197 : #ifdef USE_AVERAGE
198 0 : sumTime += time.second;
199 : #else
200 : maxTime = LB_MAX( maxTime, time.second );
201 : #endif
202 0 : LBLOG( LOG_LB2 ) << "Using " << time.first << ", " << time.second
203 0 : << "ms" << std::endl;
204 : }
205 :
206 0 : if( nSamples == _nSamples ) // If we have a full set
207 0 : while( from < ssize_t( _times.size( )))
208 0 : _times.pop_back(); // delete all older samples
209 : // always execute code above to not leak memory
210 :
211 0 : if( isFrozen() || !compound->isActive() || !isActive( ))
212 : {
213 0 : compound->setMaxFPS( std::numeric_limits< float >::max( ));
214 0 : return;
215 : }
216 :
217 0 : if( nSamples > 0 )
218 : {
219 : //TODO: totalTime *= 1.f - damping;
220 : #ifdef USE_AVERAGE
221 0 : const float time = (sumTime / nSamples) * SLOWDOWN;
222 : #else
223 : const float time = maxTime * SLOWDOWN;
224 : #endif
225 :
226 0 : const float fps = 1000.f / time;
227 : #ifdef VSYNC_CAP
228 0 : if( fps > VSYNC_CAP )
229 0 : compound->setMaxFPS( std::numeric_limits< float >::max( ));
230 : else
231 : #endif
232 0 : compound->setMaxFPS( fps );
233 :
234 0 : LBLOG( LOG_LB2 ) << fps << " Hz from " << nSamples << "/"
235 0 : << _times.size() << " samples, " << time << "ms"
236 0 : << std::endl;
237 : }
238 :
239 0 : if( frameNumber > 0 )
240 0 : _times.push_front( FrameTime( frameNumber, 0.f ));
241 0 : LBASSERT( _times.size() < 10 );
242 : }
243 :
244 0 : void FramerateEqualizer::LoadListener::notifyLoadData(
245 : Channel* channel, const uint32_t frameNumber, const Statistics& statistics,
246 : const Viewport& /*region*/ )
247 : {
248 : // gather required load data
249 0 : int64_t startTime = std::numeric_limits< int64_t >::max();
250 0 : int64_t endTime = 0;
251 0 : for( size_t i = 0; i < statistics.size(); ++i )
252 : {
253 0 : const eq::Statistic& data = statistics[i];
254 0 : switch( data.type )
255 : {
256 : case eq::Statistic::CHANNEL_CLEAR:
257 : case eq::Statistic::CHANNEL_DRAW:
258 : case eq::Statistic::CHANNEL_ASSEMBLE:
259 : case eq::Statistic::CHANNEL_READBACK:
260 0 : startTime = LB_MIN( startTime, data.startTime );
261 0 : endTime = LB_MAX( endTime, data.endTime );
262 0 : break;
263 :
264 : default:
265 0 : break;
266 : }
267 : }
268 :
269 0 : if( startTime == std::numeric_limits< int64_t >::max( ))
270 0 : return;
271 :
272 0 : if( startTime == endTime ) // very fast draws might report 0 times
273 0 : ++endTime;
274 :
275 0 : for( std::deque< FrameTime >::iterator i = parent->_times.begin();
276 0 : i != parent->_times.end(); ++i )
277 : {
278 0 : FrameTime& frameTime = *i;
279 0 : if( frameTime.first != frameNumber )
280 0 : continue;
281 :
282 0 : const float time = static_cast< float >( endTime - startTime ) / period;
283 0 : frameTime.second = LB_MAX( frameTime.second, time );
284 0 : LBLOG( LOG_LB2 ) << "Frame " << frameNumber << " channel "
285 0 : << channel->getName() << " time " << time
286 0 : << " period " << period << std::endl;
287 : }
288 : }
289 :
290 7 : std::ostream& operator << ( std::ostream& os, const FramerateEqualizer* lb )
291 : {
292 7 : if( lb )
293 7 : os << "framerate_equalizer {}" << std::endl;
294 7 : return os;
295 : }
296 :
297 : }
298 27 : }
|