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 : namespace
37 : {
38 0 : class LoadSubscriber : public CompoundVisitor
39 : {
40 : public:
41 0 : explicit LoadSubscriber(ChannelListener* listener)
42 0 : : _listener(listener)
43 : {
44 0 : }
45 :
46 0 : virtual VisitorResult visit(Compound* compound)
47 : {
48 0 : Channel* channel = compound->getChannel();
49 0 : LBASSERT(channel);
50 0 : channel->addListener(_listener);
51 :
52 0 : return TRAVERSE_CONTINUE;
53 : }
54 :
55 : private:
56 : ChannelListener* const _listener;
57 : };
58 :
59 0 : class LoadUnsubscriber : public CompoundVisitor
60 : {
61 : public:
62 0 : explicit LoadUnsubscriber(ChannelListener* listener)
63 0 : : _listener(listener)
64 : {
65 0 : }
66 :
67 0 : virtual VisitorResult visit(Compound* compound)
68 : {
69 0 : Channel* channel = compound->getChannel();
70 0 : LBASSERT(channel);
71 0 : channel->removeListener(_listener);
72 :
73 0 : return TRAVERSE_CONTINUE;
74 : }
75 :
76 : private:
77 : ChannelListener* const _listener;
78 : };
79 : }
80 :
81 : // The smooth load balancer adapts the framerate of the compound to be the
82 : // average frame rate of all children, taking the DPlex period into account.
83 :
84 28 : FramerateEqualizer::FramerateEqualizer()
85 28 : : _nSamples(0)
86 : {
87 28 : LBINFO << "New FramerateEqualizer @" << (void*)this << std::endl;
88 28 : }
89 :
90 0 : FramerateEqualizer::FramerateEqualizer(const FramerateEqualizer& from)
91 : : Equalizer(from)
92 0 : , _nSamples(0)
93 : {
94 0 : }
95 :
96 84 : FramerateEqualizer::~FramerateEqualizer()
97 : {
98 28 : attach(0);
99 56 : }
100 :
101 84 : void FramerateEqualizer::attach(Compound* compound)
102 : {
103 84 : _exit();
104 84 : Equalizer::attach(compound);
105 84 : }
106 :
107 0 : void FramerateEqualizer::_init()
108 : {
109 0 : const Compound* compound = getCompound();
110 :
111 0 : if (_nSamples > 0 || !compound)
112 0 : return;
113 :
114 0 : _nSamples = 1;
115 :
116 : // Subscribe to child channel load events
117 0 : const Compounds& children = compound->getChildren();
118 :
119 0 : LBASSERT(_loadListeners.empty());
120 0 : _loadListeners.resize(children.size());
121 :
122 0 : for (size_t i = 0; i < children.size(); ++i)
123 : {
124 0 : Compound* child = children[i];
125 0 : const uint32_t period = child->getInheritPeriod();
126 0 : LoadListener& loadListener = _loadListeners[i];
127 :
128 0 : loadListener.parent = this;
129 0 : loadListener.period = period;
130 :
131 0 : LoadSubscriber subscriber(&loadListener);
132 0 : child->accept(subscriber);
133 :
134 0 : _nSamples = LB_MAX(_nSamples, period);
135 : }
136 :
137 0 : _nSamples = LB_MIN(_nSamples, 100);
138 : }
139 :
140 84 : void FramerateEqualizer::_exit()
141 : {
142 84 : const Compound* compound = getCompound();
143 84 : if (!compound || _nSamples == 0)
144 84 : return;
145 :
146 0 : const Compounds& children = compound->getChildren();
147 :
148 0 : LBASSERT(_loadListeners.size() == children.size());
149 0 : for (size_t i = 0; i < children.size(); ++i)
150 : {
151 0 : Compound* child = children[i];
152 0 : LoadListener& loadListener = _loadListeners[i];
153 :
154 0 : LoadUnsubscriber unsubscriber(&loadListener);
155 0 : child->accept(unsubscriber);
156 : }
157 :
158 0 : _loadListeners.clear();
159 0 : _times.clear();
160 0 : _nSamples = 0;
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 << "ms"
205 0 : << 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 << "/" << _times.size()
237 0 : << " samples, " << time << "ms" << std::endl;
238 : }
239 :
240 0 : if (frameNumber > 0)
241 0 : _times.push_front(FrameTime(frameNumber, 0.f));
242 0 : LBASSERT(_times.size() < 10);
243 : }
244 :
245 0 : void FramerateEqualizer::LoadListener::notifyLoadData(
246 : Channel* channel, const uint32_t frameNumber, const Statistics& statistics,
247 : const Viewport& /*region*/)
248 : {
249 : // gather required load data
250 0 : int64_t startTime = std::numeric_limits<int64_t>::max();
251 0 : int64_t endTime = 0;
252 0 : for (size_t i = 0; i < statistics.size(); ++i)
253 : {
254 0 : const Statistic& data = statistics[i];
255 0 : switch (data.type)
256 : {
257 : case Statistic::CHANNEL_CLEAR:
258 : case Statistic::CHANNEL_DRAW:
259 : case Statistic::CHANNEL_ASSEMBLE:
260 : case Statistic::CHANNEL_READBACK:
261 0 : startTime = LB_MIN(startTime, data.startTime);
262 0 : endTime = LB_MAX(endTime, data.endTime);
263 0 : break;
264 :
265 : default:
266 0 : break;
267 : }
268 : }
269 :
270 0 : if (startTime == std::numeric_limits<int64_t>::max())
271 0 : return;
272 :
273 0 : if (startTime == endTime) // very fast draws might report 0 times
274 0 : ++endTime;
275 :
276 0 : for (std::deque<FrameTime>::iterator i = parent->_times.begin();
277 0 : i != parent->_times.end(); ++i)
278 : {
279 0 : FrameTime& frameTime = *i;
280 0 : if (frameTime.first != frameNumber)
281 0 : continue;
282 :
283 0 : const float time = static_cast<float>(endTime - startTime) / period;
284 0 : frameTime.second = LB_MAX(frameTime.second, time);
285 0 : LBLOG(LOG_LB2) << "Frame " << frameNumber << " channel "
286 0 : << channel->getName() << " time " << time << " period "
287 0 : << period << std::endl;
288 : }
289 : }
290 :
291 14 : std::ostream& operator<<(std::ostream& os, const FramerateEqualizer* lb)
292 : {
293 14 : if (lb)
294 14 : os << "framerate_equalizer {}" << std::endl;
295 14 : return os;
296 : }
297 : }
298 60 : }
|