Line data Source code
1 :
2 : /* Copyright (c) 2008-2013, Stefan Eilemann <eile@equalizergraphics.com>
3 : * 2010, Cedric Stalder <cedric.stalder@gmail.com>
4 : *
5 : * This library is free software; you can redistribute it and/or modify it under
6 : * the terms of the GNU Lesser General Public License version 2.1 as published
7 : * by the Free Software Foundation.
8 : *
9 : * This library is distributed in the hope that it will be useful, but WITHOUT
10 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 : * details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public License
15 : * along with this library; if not, write to the Free Software Foundation, Inc.,
16 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 : */
18 :
19 : #include "treeEqualizer.h"
20 :
21 : #include "../compound.h"
22 : #include "../log.h"
23 :
24 : #include <eq/client/statistic.h>
25 : #include <lunchbox/debug.h>
26 :
27 : namespace eq
28 : {
29 : namespace server
30 : {
31 :
32 : std::ostream& operator << ( std::ostream& os, const TreeEqualizer::Node* );
33 :
34 : // The tree load balancer organizes the children in a binary tree. At each
35 : // level, a relative split position is determined by balancing the left subtree
36 : // against the right subtree.
37 :
38 0 : TreeEqualizer::TreeEqualizer()
39 0 : : _tree( 0 )
40 : {
41 0 : LBINFO << "New TreeEqualizer @" << (void*)this << std::endl;
42 0 : }
43 :
44 0 : TreeEqualizer::TreeEqualizer( const TreeEqualizer& from )
45 : : Equalizer( from )
46 : , ChannelListener( from )
47 0 : , _tree( 0 )
48 0 : {}
49 :
50 0 : TreeEqualizer::~TreeEqualizer()
51 : {
52 0 : _clearTree( _tree );
53 0 : delete _tree;
54 0 : _tree = 0;
55 0 : }
56 :
57 0 : void TreeEqualizer::notifyUpdatePre( Compound* compound,
58 : const uint32_t /*frame*/ )
59 : {
60 0 : if( isFrozen() || !compound->isActive( ) || !isActive( ))
61 0 : return;
62 :
63 0 : if( !_tree )
64 : {
65 0 : LBASSERT( compound == getCompound( ));
66 0 : const Compounds& children = compound->getChildren();
67 0 : switch( children.size( ))
68 : {
69 0 : case 0: return; // no leaf compound, can't do anything.
70 : case 1: // one child, 'balance' it:
71 0 : if( getMode() == MODE_DB )
72 0 : children.front()->setRange( Range( ));
73 : else
74 0 : children.front()->setViewport( Viewport( ));
75 0 : return;
76 : default:
77 0 : _tree = _buildTree( children );
78 : }
79 : }
80 :
81 : // compute new data
82 0 : _update( _tree );
83 0 : _split( _tree );
84 0 : _assign( _tree, Viewport(), Range( ));
85 0 : LBLOG( LOG_LB2 ) << "LB tree: " << _tree;
86 : }
87 :
88 0 : TreeEqualizer::Node* TreeEqualizer::_buildTree( const Compounds& compounds )
89 : {
90 0 : Node* node = new Node;
91 :
92 0 : const size_t size = compounds.size();
93 0 : if( size == 1 )
94 : {
95 0 : Compound* compound = compounds.front();
96 :
97 0 : node->compound = compound;
98 :
99 0 : Channel* channel = compound->getChannel();
100 0 : LBASSERT( channel );
101 0 : channel->addListener( this );
102 0 : return node;
103 : }
104 :
105 0 : const size_t middle = size >> 1;
106 :
107 0 : Compounds left;
108 0 : for( size_t i = 0; i < middle; ++i )
109 0 : left.push_back( compounds[i] );
110 :
111 0 : Compounds right;
112 0 : for( size_t i = middle; i < size; ++i )
113 0 : right.push_back( compounds[i] );
114 :
115 0 : node->left = _buildTree( left );
116 0 : node->right = _buildTree( right );
117 0 : return node;
118 : }
119 :
120 0 : void TreeEqualizer::_clearTree( Node* node )
121 : {
122 0 : if( !node )
123 0 : return;
124 :
125 0 : if( node->compound )
126 : {
127 0 : Channel* channel = node->compound->getChannel();
128 0 : LBASSERTINFO( channel, node->compound );
129 0 : channel->removeListener( this );
130 : }
131 : else
132 : {
133 0 : _clearTree( node->left );
134 0 : _clearTree( node->right );
135 : }
136 : }
137 :
138 0 : void TreeEqualizer::notifyLoadData( Channel* channel,
139 : const uint32_t /*frame*/,
140 : const Statistics& statistics,
141 : const Viewport& /*region*/ )
142 : {
143 0 : _notifyLoadData( _tree, channel, statistics );
144 0 : }
145 :
146 0 : void TreeEqualizer::_notifyLoadData( Node* node, Channel* channel,
147 : const Statistics& statistics )
148 : {
149 0 : if( !node )
150 0 : return;
151 :
152 0 : _notifyLoadData( node->left, channel, statistics );
153 0 : _notifyLoadData( node->right, channel, statistics );
154 :
155 0 : if( !node->compound || node->compound->getChannel() != channel )
156 0 : return;
157 :
158 : // gather relevant load data
159 0 : const uint32_t taskID = node->compound->getTaskID();
160 0 : int64_t startTime = std::numeric_limits< int64_t >::max();
161 0 : int64_t endTime = 0;
162 0 : bool loadSet = false;
163 0 : int64_t timeTransmit = 0;
164 0 : for( size_t i = 0; i < statistics.size() && !loadSet; ++i )
165 : {
166 0 : const Statistic& stat = statistics[ i ];
167 0 : if( stat.task != taskID ) // from different compound
168 0 : continue;
169 :
170 0 : switch( stat.type )
171 : {
172 : case Statistic::CHANNEL_CLEAR:
173 : case Statistic::CHANNEL_DRAW:
174 : case Statistic::CHANNEL_READBACK:
175 0 : startTime = LB_MIN( startTime, stat.startTime );
176 0 : endTime = LB_MAX( endTime, stat.endTime );
177 0 : break;
178 :
179 : case Statistic::CHANNEL_ASYNC_READBACK:
180 : case Statistic::CHANNEL_FRAME_TRANSMIT:
181 0 : timeTransmit += stat.endTime - stat.startTime;
182 0 : break;
183 :
184 : // assemble blocks on input frames, stop using subsequent data
185 : case Statistic::CHANNEL_ASSEMBLE:
186 0 : loadSet = true;
187 0 : break;
188 :
189 : default:
190 0 : break;
191 : }
192 : }
193 :
194 0 : if( startTime == std::numeric_limits< int64_t >::max( ))
195 0 : return;
196 :
197 0 : node->time = endTime - startTime;
198 0 : node->time = LB_MAX( node->time, 1 );
199 0 : node->time = LB_MAX( node->time, timeTransmit );
200 : }
201 :
202 0 : void TreeEqualizer::_update( Node* node )
203 : {
204 0 : if( !node )
205 0 : return;
206 :
207 0 : const Compound* compound = node->compound;
208 0 : if( compound )
209 : {
210 0 : const Channel* channel = compound->getChannel();
211 0 : const PixelViewport& pvp = channel->getPixelViewport();
212 0 : LBASSERT( channel );
213 :
214 0 : LBASSERT( node->mode != MODE_2D );
215 0 : node->resources = compound->isActive() ? compound->getUsage() : 0.f;
216 0 : node->maxSize.x() = pvp.w;
217 0 : node->maxSize.y() = pvp.h;
218 0 : node->boundaryf = getBoundaryf();
219 0 : node->boundary2i = getBoundary2i();
220 0 : node->resistancef = getResistancef();
221 0 : node->resistance2i = getResistance2i();
222 0 : return;
223 : }
224 : // else
225 :
226 0 : LBASSERT( node->left );
227 0 : LBASSERT( node->right );
228 :
229 0 : node->left->mode = node->right->mode = node->mode = getMode();
230 :
231 0 : if( node->mode == MODE_2D )
232 0 : node->mode = MODE_VERTICAL;
233 :
234 0 : if( node->left->mode == MODE_2D )
235 : {
236 0 : LBASSERT( node->right->mode == MODE_2D );
237 :
238 0 : node->left->mode = (node->mode == MODE_VERTICAL) ? MODE_HORIZONTAL :
239 0 : MODE_VERTICAL;
240 0 : node->right->mode = node->left->mode;
241 : }
242 :
243 0 : _update( node->left );
244 0 : _update( node->right );
245 :
246 0 : node->resources = node->left->resources + node->right->resources;
247 :
248 0 : if( node->left->resources == 0.f )
249 : {
250 0 : node->maxSize = node->right->maxSize;
251 0 : node->boundary2i = node->right->boundary2i;
252 0 : node->boundaryf = node->right->boundaryf;
253 0 : node->resistance2i = node->right->resistance2i;
254 0 : node->resistancef = node->right->resistancef;
255 0 : node->time = node->right->time;
256 : }
257 0 : else if( node->right->resources == 0.f )
258 : {
259 0 : node->maxSize = node->left->maxSize;
260 0 : node->boundary2i = node->left->boundary2i;
261 0 : node->boundaryf = node->left->boundaryf;
262 0 : node->resistance2i = node->left->resistance2i;
263 0 : node->resistancef = node->left->resistancef;
264 0 : node->time = node->left->time;
265 : }
266 : else
267 : {
268 0 : switch( node->mode )
269 : {
270 : case MODE_VERTICAL:
271 0 : node->maxSize.x() = node->left->maxSize.x() +
272 0 : node->right->maxSize.x();
273 0 : node->maxSize.y() = LB_MIN( node->left->maxSize.y(),
274 0 : node->right->maxSize.y() );
275 0 : node->boundary2i.x() = node->left->boundary2i.x() +
276 0 : node->right->boundary2i.x();
277 0 : node->boundary2i.y() = LB_MAX( node->left->boundary2i.y(),
278 0 : node->right->boundary2i.y());
279 0 : node->boundaryf = LB_MAX( node->left->boundaryf,
280 0 : node->right->boundaryf );
281 0 : node->resistance2i.x() = LB_MAX( node->left->resistance2i.x(),
282 0 : node->right->resistance2i.x( ));
283 0 : node->resistance2i.y() = LB_MAX( node->left->resistance2i.y(),
284 0 : node->right->resistance2i.y());
285 0 : node->resistancef = LB_MAX( node->left->resistancef,
286 0 : node->right->resistancef );
287 0 : break;
288 : case MODE_HORIZONTAL:
289 0 : node->maxSize.x() = LB_MIN( node->left->maxSize.x(),
290 0 : node->right->maxSize.x() );
291 0 : node->maxSize.y() = node->left->maxSize.y() +
292 0 : node->right->maxSize.y();
293 0 : node->boundary2i.x() = LB_MAX( node->left->boundary2i.x(),
294 0 : node->right->boundary2i.x() );
295 0 : node->boundary2i.y() = node->left->boundary2i.y() +
296 0 : node->right->boundary2i.y();
297 0 : node->boundaryf = LB_MAX( node->left->boundaryf,
298 0 : node->right->boundaryf );
299 0 : node->resistance2i.x() = LB_MAX( node->left->resistance2i.x(),
300 0 : node->right->resistance2i.x() );
301 0 : node->resistance2i.y() = LB_MAX( node->left->resistance2i.y(),
302 0 : node->right->resistance2i.y( ));
303 0 : node->resistancef = LB_MAX( node->left->resistancef,
304 0 : node->right->resistancef );
305 0 : break;
306 : case MODE_DB:
307 0 : node->boundary2i.x() = LB_MAX( node->left->boundary2i.x(),
308 0 : node->right->boundary2i.x() );
309 0 : node->boundary2i.y() = LB_MAX( node->left->boundary2i.y(),
310 0 : node->right->boundary2i.y() );
311 0 : node->boundaryf = node->left->boundaryf + node->right->boundaryf;
312 0 : node->resistance2i.x() = LB_MAX( node->left->resistance2i.x(),
313 0 : node->right->resistance2i.x() );
314 0 : node->resistance2i.y() = LB_MAX( node->left->resistance2i.y(),
315 0 : node->right->resistance2i.y() );
316 0 : node->resistancef = LB_MAX( node->left->resistancef,
317 0 : node->right->resistancef );
318 0 : break;
319 : default:
320 0 : LBUNIMPLEMENTED;
321 : }
322 :
323 0 : node->time = node->left->time + node->right->time;
324 : }
325 : }
326 :
327 0 : void TreeEqualizer::_split( Node* node )
328 : {
329 0 : if( node->compound )
330 0 : return;
331 0 : LBASSERT( node->left && node->right );
332 :
333 0 : Node* left = node->left;
334 0 : Node* right = node->right;
335 : // easy outs
336 0 : if( left->resources == 0.f )
337 : {
338 0 : node->split = 0.f;
339 0 : return;
340 : }
341 0 : if( right->resources == 0.f )
342 : {
343 0 : node->split = 1.f;
344 0 : return;
345 : }
346 :
347 : // new split
348 0 : const float target = node->time * left->resources / node->resources;
349 0 : const float leftTime = float(left->time);
350 0 : float split = 0.f;
351 0 : const float rightTime = float(right->time);
352 :
353 0 : if( leftTime >= target )
354 0 : split = target / leftTime * node->split;
355 : else
356 : {
357 0 : const float timeLeft = target - leftTime;
358 0 : split = node->split + timeLeft / rightTime * ( 1.f - node->split );
359 : }
360 :
361 0 : LBLOG( LOG_LB2 )
362 0 : << "Should split at " << split << " (" << target << ": " << leftTime
363 0 : << " by " << left->resources << "/" << rightTime << " by "
364 0 : << right->resources << ")" << std::endl;
365 0 : node->split = (1.f - getDamping( )) * split + getDamping() * node->split;
366 0 : LBLOG( LOG_LB2 ) << "Dampened split at " << node->split << std::endl;
367 :
368 0 : _split( left );
369 0 : _split( right );
370 : }
371 :
372 0 : void TreeEqualizer::_assign( Node* node, const Viewport& vp,
373 : const Range& range )
374 : {
375 0 : LBLOG( LOG_LB2 ) << "assign " << vp << ", " << range << " time "
376 0 : << node->time << " split " << node->split << std::endl;
377 0 : LBASSERTINFO( vp.isValid(), vp );
378 0 : LBASSERTINFO( range.isValid(), range );
379 0 : LBASSERTINFO( node->resources > 0.f || !vp.hasArea() || !range.hasData(),
380 : "Assigning work to unused compound: " << vp << ", " << range);
381 :
382 0 : Compound* compound = node->compound;
383 0 : if( compound )
384 : {
385 0 : LBASSERTINFO( vp == Viewport::FULL || range == Range::ALL,
386 : "Mixed 2D/DB load-balancing not implemented" );
387 :
388 0 : compound->setViewport( vp );
389 0 : compound->setRange( range );
390 0 : LBLOG( LOG_LB2 ) << compound->getChannel()->getName() << " set " << vp
391 0 : << ", " << range << std::endl;
392 0 : return;
393 : }
394 :
395 0 : switch( node->mode )
396 : {
397 : case MODE_VERTICAL:
398 : {
399 : // Ensure minimum size
400 0 : const Compound* root = getCompound();
401 0 : const float pvpW = float( root->getInheritPixelViewport().w );
402 0 : const float end = vp.getXEnd();
403 0 : const float boundary = float( node->boundary2i.x( )) / pvpW;
404 0 : float absoluteSplit = vp.x + vp.w * node->split;
405 :
406 0 : if( node->left->resources == 0.f )
407 0 : absoluteSplit = vp.x;
408 0 : else if( node->right->resources == 0.f )
409 0 : absoluteSplit = end;
410 0 : else if( boundary > 0 )
411 : {
412 0 : const float right = vp.getXEnd() - absoluteSplit;
413 0 : const float left = absoluteSplit - vp.x;
414 0 : const float maxRight = float( node->right->maxSize.x( )) / pvpW;
415 0 : const float maxLeft = float( node->left->maxSize.x( )) / pvpW;
416 :
417 0 : if( right > maxRight )
418 0 : absoluteSplit = end - maxRight;
419 0 : else if( left > maxLeft )
420 0 : absoluteSplit = vp.x + maxLeft;
421 :
422 0 : if( (absoluteSplit - vp.x) < boundary )
423 0 : absoluteSplit = vp.x + boundary;
424 0 : if( (end - absoluteSplit) < boundary )
425 0 : absoluteSplit = end - boundary;
426 :
427 0 : const uint32_t ratio = uint32_t( absoluteSplit / boundary + .5f );
428 0 : absoluteSplit = ratio * boundary;
429 : }
430 :
431 0 : absoluteSplit = LB_MAX( absoluteSplit, vp.x );
432 0 : absoluteSplit = LB_MIN( absoluteSplit, end);
433 :
434 0 : const float newPixelW = pvpW * node->split;
435 0 : const float oldPixelW = pvpW * node->oldsplit;
436 0 : if( int( fabs(newPixelW - oldPixelW) ) < node->resistance2i.x( ))
437 : {
438 0 : absoluteSplit = vp.x + vp.w * node->oldsplit;
439 0 : node->split = node->oldsplit;
440 : }
441 : else
442 : {
443 0 : node->split = (absoluteSplit - vp.x ) / vp.w;
444 0 : node->oldsplit = node->split;
445 : }
446 :
447 0 : LBLOG( LOG_LB2 ) << "Constrained split " << vp << " at X "
448 0 : << node->split << std::endl;
449 :
450 : // traverse children
451 0 : Viewport childVP = vp;
452 0 : childVP.w = (absoluteSplit - vp.x);
453 0 : _assign( node->left, childVP, range );
454 :
455 0 : childVP.x = childVP.getXEnd();
456 0 : childVP.w = end - childVP.x;
457 :
458 : // Fix 2994111: Rounding errors with 2D LB and 16 sources
459 : // Floating point rounding may create a width for the 'right'
460 : // child which is slightly below the parent width. Correct it.
461 0 : while( childVP.getXEnd() < end )
462 0 : childVP.w += std::numeric_limits< float >::epsilon();
463 :
464 0 : _assign( node->right, childVP, range );
465 0 : break;
466 : }
467 :
468 : case MODE_HORIZONTAL:
469 : {
470 : // Ensure minimum size
471 0 : const Compound* root = getCompound();
472 0 : const float pvpH = float( root->getInheritPixelViewport().h );
473 0 : const float end = vp.getYEnd();
474 0 : const float boundary = float( node->boundary2i.y( )) / pvpH;
475 0 : float absoluteSplit = vp.y + vp.h * node->split;
476 :
477 0 : if( node->left->resources == 0.f )
478 0 : absoluteSplit = vp.y;
479 0 : else if( node->right->resources == 0.f )
480 0 : absoluteSplit = end;
481 0 : else if( boundary > 0 )
482 : {
483 0 : const float right = vp.getYEnd() - absoluteSplit;
484 0 : const float left = absoluteSplit - vp.y;
485 0 : const float maxRight = float( node->right->maxSize.y( )) / pvpH;
486 0 : const float maxLeft = float( node->left->maxSize.y( )) / pvpH;
487 :
488 0 : if( right > maxRight )
489 0 : absoluteSplit = end - maxRight;
490 0 : else if( left > maxLeft )
491 0 : absoluteSplit = vp.y + maxLeft;
492 :
493 0 : if( (absoluteSplit - vp.y) < boundary )
494 0 : absoluteSplit = vp.y + boundary;
495 0 : if( (end - absoluteSplit) < boundary )
496 0 : absoluteSplit = end - boundary;
497 :
498 0 : const uint32_t ratio = uint32_t( absoluteSplit / boundary + .5f );
499 0 : absoluteSplit = ratio * boundary;
500 : }
501 :
502 0 : absoluteSplit = LB_MAX( absoluteSplit, vp.y );
503 0 : absoluteSplit = LB_MIN( absoluteSplit, end);
504 :
505 0 : const float newPixelH = pvpH * node->split;
506 0 : const float oldPixelH = pvpH * node->oldsplit;
507 0 : if( int( fabs(newPixelH - oldPixelH) ) < node->resistance2i.y( ))
508 : {
509 0 : absoluteSplit = vp.x + vp.w * node->oldsplit;
510 0 : node->split = node->oldsplit;
511 : }
512 : else
513 : {
514 0 : node->split = (absoluteSplit - vp.y ) / vp.h;
515 0 : node->oldsplit = node->split;
516 : }
517 :
518 0 : LBLOG( LOG_LB2 ) << "Constrained split " << vp << " at X "
519 0 : << node->split << std::endl;
520 :
521 : // traverse children
522 0 : Viewport childVP = vp;
523 0 : childVP.h = (absoluteSplit - vp.y);
524 0 : _assign( node->left, childVP, range );
525 :
526 0 : childVP.y = childVP.getYEnd();
527 0 : childVP.h = end - childVP.y;
528 :
529 : // Fix 2994111: Rounding errors with 2D LB and 16 sources
530 : // Floating point rounding may create a width for the 'right'
531 : // child which is slightly below the parent width. Correct it.
532 0 : while( childVP.getYEnd() < end )
533 0 : childVP.h += std::numeric_limits< float >::epsilon();
534 :
535 0 : _assign( node->right, childVP, range );
536 0 : break;
537 : }
538 :
539 : case MODE_DB:
540 : {
541 0 : LBASSERT( vp == Viewport::FULL );
542 0 : const float end = range.end;
543 0 : float absoluteSplit = range.start + (range.end-range.start)*node->split;
544 :
545 0 : const float boundary( node->boundaryf );
546 0 : if( node->left->resources == 0.f )
547 0 : absoluteSplit = range.start;
548 0 : else if( node->right->resources == 0.f )
549 0 : absoluteSplit = end;
550 :
551 0 : const uint32_t ratio = uint32_t( absoluteSplit / boundary + .5f );
552 0 : absoluteSplit = ratio * boundary;
553 0 : if( (absoluteSplit - range.start) < boundary )
554 0 : absoluteSplit = range.start;
555 0 : if( (end - absoluteSplit) < boundary )
556 0 : absoluteSplit = end;
557 :
558 0 : const float oldSplit = range.start +
559 0 : (range.end-range.start)*node->oldsplit;
560 0 : if( fabs( absoluteSplit - oldSplit ) < node->resistancef )
561 : {
562 0 : absoluteSplit = oldSplit;
563 0 : node->split = node->oldsplit;
564 : }
565 : else
566 : {
567 0 : node->split = (absoluteSplit-range.start) / (range.end-range.start);
568 0 : node->oldsplit = node->split;
569 : }
570 :
571 0 : LBLOG( LOG_LB2 ) << "Constrained split " << range << " at pos "
572 0 : << node->split << std::endl;
573 :
574 0 : Range childRange = range;
575 0 : childRange.end = absoluteSplit;
576 0 : _assign( node->left, vp, childRange );
577 :
578 0 : childRange.start = childRange.end;
579 0 : childRange.end = range.end;
580 0 : _assign( node->right, vp, childRange);
581 0 : break;
582 : }
583 :
584 : default:
585 0 : LBUNIMPLEMENTED;
586 : }
587 : }
588 :
589 0 : std::ostream& operator << ( std::ostream& os, const TreeEqualizer::Node* node )
590 : {
591 0 : if( !node )
592 0 : return os;
593 :
594 0 : os << lunchbox::disableFlush;
595 :
596 0 : if( node->compound )
597 0 : os << node->compound->getChannel()->getName() << " resources "
598 0 : << node->resources << " max size " << node->maxSize << std::endl;
599 : else
600 0 : os << "split " << node->mode << " @ " << node->split << " resources "
601 0 : << node->resources << " max size " << node->maxSize << std::endl
602 0 : << lunchbox::indent << node->left << node->right << lunchbox::exdent;
603 :
604 0 : os << lunchbox::enableFlush;
605 0 : return os;
606 : }
607 :
608 0 : std::ostream& operator << ( std::ostream& os, const TreeEqualizer* lb )
609 : {
610 0 : if( !lb )
611 0 : return os;
612 :
613 0 : os << lunchbox::disableFlush
614 0 : << "tree_equalizer" << std::endl
615 0 : << '{' << std::endl
616 0 : << " mode " << lb->getMode() << std::endl;
617 :
618 0 : if( lb->getDamping() != 0.5f )
619 0 : os << " damping " << lb->getDamping() << std::endl;
620 :
621 0 : if( lb->getBoundary2i() != Vector2i( 1, 1 ) )
622 0 : os << " boundary [ " << lb->getBoundary2i().x() << " "
623 0 : << lb->getBoundary2i().y() << " ]" << std::endl;
624 :
625 0 : if( lb->getBoundaryf() != std::numeric_limits<float>::epsilon() )
626 0 : os << " boundary " << lb->getBoundaryf() << std::endl;
627 :
628 0 : if( lb->getResistance2i() != Vector2i( 0, 0 ) )
629 0 : os << " resistance [ " << lb->getResistance2i().x() << " "
630 0 : << lb->getResistance2i().y() << " ]" << std::endl;
631 :
632 0 : if( lb->getResistancef() != .0f )
633 0 : os << " resistance " << lb->getResistancef() << std::endl;
634 :
635 0 : os << '}' << std::endl << lunchbox::enableFlush;
636 0 : return os;
637 : }
638 :
639 : }
640 27 : }
|