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/fabric/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 : explicit LoadSubscriber( ChannelListener* listener )
43 0 : : _listener( listener ) {}
44 :
45 0 : virtual VisitorResult visit( Compound* compound )
46 : {
47 0 : Channel* channel = compound->getChannel();
48 0 : LBASSERT( channel );
49 0 : channel->addListener( _listener );
50 :
51 0 : return TRAVERSE_CONTINUE;
52 : }
53 :
54 : private:
55 : ChannelListener* const _listener;
56 : };
57 :
58 0 : class LoadUnsubscriber : public CompoundVisitor
59 : {
60 : public:
61 0 : explicit LoadUnsubscriber( ChannelListener* listener )
62 0 : : _listener( listener ) {}
63 :
64 0 : virtual VisitorResult visit( Compound* compound )
65 : {
66 0 : Channel* channel = compound->getChannel();
67 0 : LBASSERT( channel );
68 0 : channel->removeListener( _listener );
69 :
70 0 : return TRAVERSE_CONTINUE;
71 : }
72 :
73 : private:
74 : ChannelListener* const _listener;
75 : };
76 :
77 : }
78 :
79 : // The smooth load balancer adapts the framerate of the compound to be the
80 : // average frame rate of all children, taking the DPlex period into account.
81 :
82 28 : FramerateEqualizer::FramerateEqualizer()
83 28 : : _nSamples( 0 )
84 : {
85 28 : LBINFO << "New FramerateEqualizer @" << (void*)this << std::endl;
86 28 : }
87 :
88 0 : FramerateEqualizer::FramerateEqualizer( const FramerateEqualizer& from )
89 : : Equalizer( from )
90 0 : , _nSamples( 0 )
91 : {
92 0 : }
93 :
94 84 : FramerateEqualizer::~FramerateEqualizer()
95 : {
96 28 : attach( 0 );
97 56 : }
98 :
99 84 : void FramerateEqualizer::attach( Compound* compound )
100 : {
101 84 : _exit();
102 84 : Equalizer::attach( compound );
103 84 : }
104 :
105 :
106 0 : void FramerateEqualizer::_init()
107 : {
108 0 : const Compound* compound = getCompound();
109 :
110 0 : if( _nSamples > 0 || !compound )
111 0 : return;
112 :
113 0 : _nSamples = 1;
114 :
115 : // Subscribe to child channel load events
116 0 : const Compounds& children = compound->getChildren();
117 :
118 0 : LBASSERT( _loadListeners.empty( ));
119 0 : _loadListeners.resize( children.size( ));
120 :
121 0 : for( size_t i = 0; i < children.size(); ++i )
122 : {
123 0 : Compound* child = children[i];
124 0 : const uint32_t period = child->getInheritPeriod();
125 0 : LoadListener& loadListener = _loadListeners[i];
126 :
127 0 : loadListener.parent = this;
128 0 : loadListener.period = period;
129 :
130 0 : LoadSubscriber subscriber( &loadListener );
131 0 : child->accept( subscriber );
132 :
133 0 : _nSamples = LB_MAX( _nSamples, period );
134 0 : }
135 :
136 0 : _nSamples = LB_MIN( _nSamples, 100 );
137 : }
138 :
139 84 : void FramerateEqualizer::_exit()
140 : {
141 84 : const Compound* compound = getCompound();
142 84 : if( !compound || _nSamples == 0 )
143 168 : return;
144 :
145 0 : const Compounds& children = compound->getChildren();
146 :
147 0 : LBASSERT( _loadListeners.size() == children.size( ));
148 0 : for( size_t i = 0; i < children.size(); ++i )
149 : {
150 0 : Compound* child = children[i];
151 0 : LoadListener& loadListener = _loadListeners[i];
152 :
153 0 : LoadUnsubscriber unsubscriber( &loadListener );
154 0 : child->accept( unsubscriber );
155 0 : }
156 :
157 0 : _loadListeners.clear();
158 0 : _times.clear();
159 0 : _nSamples = 0;
160 : }
161 :
162 :
163 0 : void FramerateEqualizer::notifyUpdatePre( Compound* compound,
164 : const uint32_t frameNumber )
165 : {
166 0 : _init();
167 :
168 : // find starting point of contiguous block
169 0 : const ssize_t size = ssize_t( _times.size( ));
170 0 : ssize_t from = 0;
171 0 : if( size > 0 )
172 : {
173 0 : for( ssize_t i = size-1; i >= 0; --i )
174 : {
175 0 : if( _times[i].second == 0.f )
176 : {
177 0 : from = i;
178 0 : break;
179 : }
180 : }
181 : }
182 :
183 : // find max / avg time in block
184 0 : size_t nSamples = 0;
185 : #ifdef USE_AVERAGE
186 0 : float sumTime = 0.f;
187 : #else
188 : float maxTime = 0.f;
189 : #endif
190 :
191 0 : LBLOG( LOG_LB2 ) << "Searching " << from+1 << ".." << size << std::endl;
192 0 : for( ++from; from < size && nSamples < _nSamples; ++from )
193 : {
194 0 : const FrameTime& time = _times[from];
195 0 : LBASSERT( time.first > 0 );
196 0 : LBASSERT( time.second != 0.f );
197 :
198 0 : ++nSamples;
199 : #ifdef USE_AVERAGE
200 0 : sumTime += time.second;
201 : #else
202 : maxTime = LB_MAX( maxTime, time.second );
203 : #endif
204 0 : LBLOG( LOG_LB2 ) << "Using " << time.first << ", " << time.second
205 0 : << "ms" << std::endl;
206 : }
207 :
208 0 : if( nSamples == _nSamples ) // If we have a full set
209 0 : while( from < ssize_t( _times.size( )))
210 0 : _times.pop_back(); // delete all older samples
211 : // always execute code above to not leak memory
212 :
213 0 : if( isFrozen() || !compound->isActive() || !isActive( ))
214 : {
215 0 : compound->setMaxFPS( std::numeric_limits< float >::max( ));
216 0 : return;
217 : }
218 :
219 0 : if( nSamples > 0 )
220 : {
221 : //TODO: totalTime *= 1.f - damping;
222 : #ifdef USE_AVERAGE
223 0 : const float time = (sumTime / nSamples) * SLOWDOWN;
224 : #else
225 : const float time = maxTime * SLOWDOWN;
226 : #endif
227 :
228 0 : const float fps = 1000.f / time;
229 : #ifdef VSYNC_CAP
230 0 : if( fps > VSYNC_CAP )
231 0 : compound->setMaxFPS( std::numeric_limits< float >::max( ));
232 : else
233 : #endif
234 0 : compound->setMaxFPS( fps );
235 :
236 0 : LBLOG( LOG_LB2 ) << fps << " Hz from " << nSamples << "/"
237 0 : << _times.size() << " samples, " << time << "ms"
238 0 : << std::endl;
239 : }
240 :
241 0 : if( frameNumber > 0 )
242 0 : _times.push_front( FrameTime( frameNumber, 0.f ));
243 0 : LBASSERT( _times.size() < 10 );
244 : }
245 :
246 0 : void FramerateEqualizer::LoadListener::notifyLoadData(
247 : Channel* channel, const uint32_t frameNumber, const Statistics& statistics,
248 : const Viewport& /*region*/ )
249 : {
250 : // gather required load data
251 0 : int64_t startTime = std::numeric_limits< int64_t >::max();
252 0 : int64_t endTime = 0;
253 0 : for( size_t i = 0; i < statistics.size(); ++i )
254 : {
255 0 : const Statistic& data = statistics[i];
256 0 : switch( data.type )
257 : {
258 : case Statistic::CHANNEL_CLEAR:
259 : case Statistic::CHANNEL_DRAW:
260 : case Statistic::CHANNEL_ASSEMBLE:
261 : case Statistic::CHANNEL_READBACK:
262 0 : startTime = LB_MIN( startTime, data.startTime );
263 0 : endTime = LB_MAX( endTime, data.endTime );
264 0 : break;
265 :
266 : default:
267 0 : break;
268 : }
269 : }
270 :
271 0 : if( startTime == std::numeric_limits< int64_t >::max( ))
272 0 : return;
273 :
274 0 : if( startTime == endTime ) // very fast draws might report 0 times
275 0 : ++endTime;
276 :
277 0 : for( std::deque< FrameTime >::iterator i = parent->_times.begin();
278 0 : i != parent->_times.end(); ++i )
279 : {
280 0 : FrameTime& frameTime = *i;
281 0 : if( frameTime.first != frameNumber )
282 0 : continue;
283 :
284 0 : const float time = static_cast< float >( endTime - startTime ) / period;
285 0 : frameTime.second = LB_MAX( frameTime.second, time );
286 0 : LBLOG( LOG_LB2 ) << "Frame " << frameNumber << " channel "
287 0 : << channel->getName() << " time " << time
288 0 : << " period " << period << std::endl;
289 : }
290 : }
291 :
292 14 : std::ostream& operator << ( std::ostream& os, const FramerateEqualizer* lb )
293 : {
294 14 : if( lb )
295 14 : os << "framerate_equalizer {}" << std::endl;
296 14 : return os;
297 : }
298 :
299 : }
300 84 : }
|