void LoadEqualizer::_computeSplit()
{
    LBASSERT( !_history.empty( ));

    const LBFrameData& frameData = _history.front();
    const Compound* compound = getCompound();
    LBLOG( LOG_LB2 ) << "----- balance " << compound->getChannel()->getName()
                    << " using frame " << frameData.first << " tree "
                     << std::endl << _tree;

    // sort load items for each of the split directions
    LBDatas items( frameData.second );
    _removeEmpty( items );

    LBDatas sortedData[3] = { items, items, items };

    if( getMode() == MODE_DB )
    {
        LBDatas& rangeData = sortedData[ MODE_DB ];
        sort( rangeData.begin(), rangeData.end(), _compareRange );
    }
    else
    {
        LBDatas& xData = sortedData[ MODE_VERTICAL ];
        sort( xData.begin(), xData.end(), _compareX );

        LBDatas& yData = sortedData[ MODE_HORIZONTAL ];
        sort( yData.begin(), yData.end(), _compareY );

#ifndef NDEBUG
        for( LBDatas::const_iterator i = xData.begin(); i != xData.end();
             ++i )
        {
            const Data& data = *i;
            LBLOG( LOG_LB2 ) << "  " << data.vp << ", time " << data.time
                             << " (+" << data.assembleTime << ")" << std::endl;
        }
#endif
    }

    const float time = float( _getTotalTime( ));
    LBLOG( LOG_LB2 ) << "Render time " << time << " for "
                     << _tree->resources << " resources" << std::endl;
    if( _tree->resources > 0.f )
        _computeSplit( _tree, time, sortedData, Viewport(), Range( ));
}
void LoadEqualizer::_computeSplit( Node* node, const float time,
                                   LBDatas* datas, const Viewport& vp,
                                   const Range& range )
{
    LBLOG( LOG_LB2 ) << "_computeSplit " << vp << ", " << range << " time "
                    << time << std::endl;
    LBASSERTINFO( vp.isValid(), vp );
    LBASSERTINFO( range.isValid(), range );
    LBASSERTINFO( node->resources > 0.f || !vp.hasArea() || !range.hasData(),
                  "Assigning " << node->resources <<
                  " work to viewport " << vp << ", " << range );

    Compound* compound = node->compound;
    if( compound )
    {
        _assign( compound, vp, range );
        return;
    }

    LBASSERT( node->left && node->right );

    LBDatas workingSet = datas[ node->mode ];
    const float leftTime = node->resources > 0 ?
                           time * node->left->resources / node->resources : 0.f;
    float timeLeft = LB_MIN( leftTime, time ); // correct for fp rounding error

    switch( node->mode )
    {
        case MODE_VERTICAL:
        {
            LBASSERT( range == Range::ALL );

            float splitPos = vp.x;
            const float end = vp.getXEnd();

            while( timeLeft > std::numeric_limits< float >::epsilon() &&
                   splitPos < end )
            {
                LBLOG( LOG_LB2 ) << timeLeft << "ms left using "
                                << workingSet.size() << " tiles" << std::endl;

                // remove all irrelevant items from working set
                for( LBDatas::iterator i = workingSet.begin();
                     i != workingSet.end(); )
                {
                    const Data& data = *i;
                    if( data.vp.getXEnd() > splitPos )
                        ++i;
                    else
                        i = workingSet.erase( i );
                }
                if( workingSet.empty( ))
                    break;

                // find next 'discontinouity' in loads
                float currentPos = 1.0f;
                for( LBDatas::const_iterator i = workingSet.begin();
                     i != workingSet.end(); ++i )
                {
                    const Data& data = *i;
                    if( data.vp.x > splitPos && data.vp.x < currentPos )
                        currentPos = data.vp.x;
                    const float xEnd = data.vp.getXEnd();
                    if( xEnd > splitPos && xEnd < currentPos )
                        currentPos = xEnd;
                }

                const float width = currentPos - splitPos;
                LBASSERTINFO( width > 0.f, currentPos << "<=" << splitPos );
                LBASSERT( currentPos <= 1.0f );

                // accumulate normalized load in splitPos...currentPos
                LBLOG( LOG_LB2 ) << "Computing load in X " << splitPos << "..."
                                 << currentPos << std::endl;
                float currentTime = 0.f;
                for( LBDatas::const_iterator i = workingSet.begin();
                     i != workingSet.end(); ++i )
                {
                    const Data& data = *i;

                    if( data.vp.x >= currentPos ) // not yet needed data sets
                        break;

                    float yContrib = data.vp.h;
                    if( data.vp.y < vp.y )
                        yContrib -= (vp.y - data.vp.y);

                    const float dataEnd = data.vp.getYEnd();
                    const float vpEnd   = vp.getYEnd();
                    if( dataEnd > vpEnd )
                        yContrib -= (dataEnd - vpEnd);

                    if( yContrib > 0.f )
                    {
                        const float percentage = ( width / data.vp.w ) *
                                                 ( yContrib / data.vp.h );
                        currentTime += ( data.time * percentage );

                        LBLOG( LOG_LB2 ) << data.vp << " contributes "
                                         << yContrib << " in " << vp.h << " ("
                                         << percentage << ") with " << data.time
                                         << ": " << ( data.time * percentage )
                                         << " vp.y " << vp.y << " dataEnd "
                                         << dataEnd << " vpEnd " << vpEnd
                                         << std::endl;
                        LBASSERT( percentage < 1.01f )
                    }
                }

                LBLOG( LOG_LB2 ) << splitPos << "..." << currentPos << ": t="
                                 << currentTime << " of " << timeLeft
                                 << std::endl;

                if( currentTime >= timeLeft ) // found last region
                {
                    splitPos += ( width * timeLeft / currentTime );
                    timeLeft = 0.0f;
                }
                else
                {
                    timeLeft -= currentTime;
                    splitPos  = currentPos;
                }
            }

            LBLOG( LOG_LB2 ) << "Should split at X " << splitPos << std::endl;
            if( getDamping() < 1.f )
                splitPos = (1.f - getDamping()) * splitPos +
                            getDamping() * node->split;
            LBLOG( LOG_LB2 ) << "Dampened split at X " << splitPos << std::endl;

            // There might be more time left due to MIN_PIXEL rounding by parent
            // LBASSERTINFO( timeLeft <= .001f, timeLeft );

            // Ensure minimum size
            const Compound* root = getCompound();
            const float pvpW = static_cast< float >(
                root->getInheritPixelViewport().w );
            const float boundary = static_cast< float >( node->boundary2i.x()) /
                                       pvpW;
            if( node->left->resources == 0.f )
                splitPos = vp.x;
            else if( node->right->resources == 0.f )
                splitPos = end;
            else if( boundary > 0 )
            {
                const float lengthRight = vp.getXEnd() - splitPos;
                const float lengthLeft = splitPos - vp.x;
                const float maxRight =
                    static_cast< float >( node->right->maxSize.x( )) / pvpW;
                const float maxLeft =
                    static_cast< float >( node->left->maxSize.x( )) / pvpW;
                if( lengthRight > maxRight )
                    splitPos = end - maxRight;
                else if( lengthLeft > maxLeft )
                    splitPos = vp.x + maxLeft;

                if( (splitPos - vp.x) < boundary )
                    splitPos = vp.x + boundary;
                if( (end - splitPos) < boundary )
                    splitPos = end - boundary;

                const uint32_t ratio =
                           static_cast< uint32_t >( splitPos / boundary + .5f );
                splitPos = ratio * boundary;
            }

            splitPos = LB_MAX( splitPos, vp.x );
            splitPos = LB_MIN( splitPos, end);

            const float newPixelW = pvpW * splitPos;
            const float oldPixelW = pvpW * node->split;
            if( int( fabs(newPixelW - oldPixelW) ) < node->resistance2i.x( ))
                splitPos = node->split;
            else
                node->split = splitPos;

            LBLOG( LOG_LB2 ) << "Constrained split " << vp << " at X "
                             << splitPos << std::endl;

            // balance children
            Viewport childVP = vp;
            childVP.w = (splitPos - vp.x);
            _computeSplit( node->left, leftTime, datas, childVP, range );

            childVP.x = childVP.getXEnd();
            childVP.w = end - childVP.x;
            // Fix 2994111: Rounding errors with 2D LB and 16 sources
            //   Floating point rounding may create a width for the 'right'
            //   child which is slightly below the parent width. Correct it.
            while( childVP.getXEnd() < end )
                childVP.w += std::numeric_limits< float >::epsilon();
            _computeSplit( node->right, time-leftTime, datas, childVP, range );
            break;
        }

        case MODE_HORIZONTAL:
        {
            LBASSERT( range == Range::ALL );
            float splitPos = vp.y;
            const float end = vp.getYEnd();

            while( timeLeft > std::numeric_limits< float >::epsilon() &&
                   splitPos < end )
            {
                LBLOG( LOG_LB2 ) << timeLeft << "ms left using "
                                 << workingSet.size() << " tiles" << std::endl;

                // remove all unrelevant items from working set
                for( LBDatas::iterator i = workingSet.begin();
                     i != workingSet.end(); )
                {
                    const Data& data = *i;
                    if( data.vp.getYEnd() > splitPos )
                        ++i;
                    else
                        i = workingSet.erase( i );
                }
                if( workingSet.empty( ))
                    break;

                // find next 'discontinuouity' in loads
                float currentPos = 1.0f;
                for( LBDatas::const_iterator i = workingSet.begin();
                     i != workingSet.end(); ++i )
                {
                    const Data& data = *i;
                    if( data.vp.y > splitPos && data.vp.y < currentPos )
                        currentPos = data.vp.y;
                    const float yEnd = data.vp.getYEnd();
                    if( yEnd > splitPos && yEnd < currentPos )
                        currentPos = yEnd;
                }

                const float height = currentPos - splitPos;
                LBASSERTINFO( height > 0.f, currentPos << "<=" << splitPos );
                LBASSERT( currentPos <= 1.0f );

                // accumulate normalized load in splitPos...currentPos
                LBLOG( LOG_LB2 ) << "Computing load in Y " << splitPos << "..."
                                << currentPos << std::endl;
                float currentTime = 0.f;
                for( LBDatas::const_iterator i = workingSet.begin();
                     i != workingSet.end(); ++i )
                {
                    const Data& data = *i;

                    if( data.vp.y >= currentPos ) // not yet needed data sets
                        break;

                    float xContrib = data.vp.w;

                    if( data.vp.x < vp.x )
                        xContrib -= (vp.x - data.vp.x);

                    const float dataEnd = data.vp.getXEnd();
                    const float vpEnd   = vp.getXEnd();
                    if( dataEnd > vpEnd )
                        xContrib -= (dataEnd - vpEnd);

                    if( xContrib > 0.f )
                    {
                        const float percentage = ( height / data.vp.h ) *
                                                 ( xContrib / data.vp.w );
                        currentTime += ( data.time * percentage );

                        LBLOG( LOG_LB2 ) << data.vp << " contributes "
                                         << xContrib << " in " << vp.w << " ("
                                         << percentage << ") with " << data.time
                                         << ": " << ( data.time * percentage )
                                         << " total " << currentTime << " vp.x "
                                         << vp.x << " dataEnd " << dataEnd
                                         << " vpEnd " << vpEnd << std::endl;
                        LBASSERT( percentage < 1.01f )
                    }
                }

                LBLOG( LOG_LB2 ) << splitPos << "..." << currentPos << ": t="
                                 << currentTime << " of " << timeLeft
                                 << std::endl;

                if( currentTime >= timeLeft ) // found last region
                {
                    splitPos += (height * timeLeft / currentTime );
                    timeLeft = 0.0f;
                }
                else
                {
                    timeLeft -= currentTime;
                    splitPos  = currentPos;
                }
            }

            LBLOG( LOG_LB2 ) << "Should split at Y " << splitPos << std::endl;
            if( getDamping() < 1.f )
                splitPos = (1.f - getDamping( )) * splitPos +
                            getDamping() * node->split;
            LBLOG( LOG_LB2 ) << "Dampened split at Y " << splitPos << std::endl;

            const Compound* root = getCompound();

            const float pvpH = static_cast< float >(
                root->getInheritPixelViewport().h );
            const float boundary = static_cast< float >(node->boundary2i.y( )) /
                                       pvpH;

            if( node->left->resources == 0.f )
                splitPos = vp.y;
            else if( node->right->resources == 0.f )
                splitPos = end;
            else if ( boundary > 0 )
            {
                const float lengthRight = vp.getYEnd() - splitPos;
                const float lengthLeft = splitPos - vp.y;
                const float maxRight =
                    static_cast< float >( node->right->maxSize.y( )) / pvpH;
                const float maxLeft =
                    static_cast< float >( node->left->maxSize.y( )) / pvpH;
                if( lengthRight > maxRight )
                    splitPos = end - maxRight;
                else if( lengthLeft > maxLeft )
                    splitPos = vp.y + maxLeft;

                if( (splitPos - vp.y) < boundary )
                    splitPos = vp.y + boundary;
                if( (end - splitPos) < boundary )
                    splitPos = end - boundary;

                const uint32_t ratio =
                           static_cast< uint32_t >( splitPos / boundary + .5f );
                splitPos = ratio * boundary;
            }

            splitPos = LB_MAX( splitPos, vp.y );
            splitPos = LB_MIN( splitPos, end );

            const float newPixelH = pvpH * splitPos;
            const float oldPixelH = pvpH * node->split;
            if( int( fabs(newPixelH - oldPixelH) ) < node->resistance2i.y( ))
                splitPos = node->split;
            else
                node->split = splitPos;

            LBLOG( LOG_LB2 ) << "Constrained split " << vp << " at Y "
                             << splitPos << std::endl;

            Viewport childVP = vp;
            childVP.h = (splitPos - vp.y);
            _computeSplit( node->left, leftTime, datas, childVP, range );

            childVP.y = childVP.getYEnd();
            childVP.h = end - childVP.y;
            while( childVP.getYEnd() < end )
                childVP.h += std::numeric_limits< float >::epsilon();
            _computeSplit( node->right, time - leftTime, datas, childVP, range);
            break;
        }

        case MODE_DB:
        {
            LBASSERT( vp == Viewport::FULL );
            float splitPos = range.start;
            const float end = range.end;

            while( timeLeft > std::numeric_limits< float >::epsilon() &&
                   splitPos < end )
            {
                LBLOG( LOG_LB2 ) << timeLeft << "ms left using "
                                 << workingSet.size() << " tiles" << std::endl;

                // remove all irrelevant items from working set
                for( LBDatas::iterator i = workingSet.begin();
                     i != workingSet.end(); )
                {
                    const Data& data = *i;
                    if( data.range.end > splitPos )
                        ++i;
                    else
                        i = workingSet.erase( i );
                }
                if( workingSet.empty( ))
                    break;

                // find next 'discontinouity' in loads
                float currentPos = 1.0f;
                for( LBDatas::const_iterator i = workingSet.begin();
                     i != workingSet.end(); ++i )
                {
                    const Data& data = *i;
                    currentPos = LB_MIN( currentPos, data.range.end );
                }

                const float size = currentPos - splitPos;
                LBASSERTINFO( size > 0.f, currentPos << "<=" << splitPos );
                LBASSERT( currentPos <= 1.0f );

                // accumulate normalized load in splitPos...currentPos
                LBLOG( LOG_LB2 ) << "Computing load in range " << splitPos
                                << "..." << currentPos << std::endl;
                float currentTime = 0.f;
                for( LBDatas::const_iterator i = workingSet.begin();
                     i != workingSet.end(); ++i )
                {
                    const Data& data = *i;

                    if( data.range.start >= currentPos ) // not yet needed data
                        break;
#if 0
                    // make sure we cover full area
                    LBASSERTINFO(  data.range.start <= splitPos,
                                   data.range.start << " > " << splitPos );
                    LBASSERTINFO( data.range.end >= currentPos,
                                  data.range.end << " < " << currentPos);
#endif
                    currentTime += data.time * size / data.range.getSize();
                }

                LBLOG( LOG_LB2 ) << splitPos << "..." << currentPos << ": t="
                                 << currentTime << " of " << timeLeft
                                 << std::endl;

                if( currentTime >= timeLeft ) // found last region
                {
                    const float width = currentPos - splitPos;
                    splitPos += (width * timeLeft / currentTime );
                    timeLeft = 0.0f;
                }
                else
                {
                    timeLeft -= currentTime;
                    splitPos  = currentPos;
                }
            }
            LBLOG( LOG_LB2 ) << "Should split at " << splitPos << std::endl;
            if( getDamping() < 1.f )
                splitPos = (1.f - getDamping( )) * splitPos +
                            getDamping() * node->split;
            LBLOG( LOG_LB2 ) << "Dampened split at " << splitPos << std::endl;

            const float boundary( node->boundaryf );
            if( node->left->resources == 0.f )
                splitPos = range.start;
            else if( node->right->resources == 0.f )
                splitPos = end;

            const uint32_t ratio = static_cast< uint32_t >
                      ( splitPos / boundary + .5f );
            splitPos = ratio * boundary;
            if( (splitPos - range.start) < boundary )
                splitPos = range.start;
            if( (end - splitPos) < boundary )
                splitPos = end;

            if( fabs( splitPos - node->split ) < node->resistancef )
                splitPos = node->split;
            else
                node->split = splitPos;

            LBLOG( LOG_LB2 ) << "Constrained split " << range << " at pos "
                             << splitPos << std::endl;

            Range childRange = range;
            childRange.end = splitPos;
            _computeSplit( node->left, leftTime, datas, vp, childRange );

            childRange.start = childRange.end;
            childRange.end   = range.end;
            _computeSplit( node->right, time - leftTime, datas, vp, childRange);
            break;
        }

        default:
            LBUNIMPLEMENTED;
    }
}