1. 程式人生 > >osgEarth的Rex引擎原理分析(三十三)分頁瓦片解除安裝器子節點的作用

osgEarth的Rex引擎原理分析(三十三)分頁瓦片解除安裝器子節點的作用

目標:(十二)中的問題22

分頁瓦片解除安裝器是在Rex引擎的setMap函式中建立的,建立之初就關聯了活躍瓦片暫存器和資源釋放器。作用見下面分析。

osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
void
RexTerrainEngineNode::setMap(const Map* map, const TerrainOptions& options)
{  
    // Make a tile unloader
    _unloader = new UnloaderGroup( _liveTiles.get() );
    _unloader->setThreshold( _terrainOptions.expirationThreshold().get() );
    _unloader->setReleaser(_releaser.get());
    this->addChild( _unloader.get() );
}

1、渲染遍歷時在RexTerrainEngineNode裁剪遍歷階段向解除安裝器中新增要刪除的瓦片節點,如何確定要刪除的瓦片節點 由RexTerrainEngineNode的上下文環境中的endCull函式中的掃描器來確定(endCull最終是在RexTerrainEngineNode在渲染遍歷時呼叫):

osgEarthDrivers/engine_rex/EngineContext.cpp
void
EngineContext::endCull(osgUtil::CullVisitor* cv)
{
    std::vector<TileKey> tilesWithChildrenToUnload;
    Scanner scanner(tilesWithChildrenToUnload, cv->getFrameStamp());
    _liveTiles->run( scanner );
    if ( !tilesWithChildrenToUnload.empty() )
    {        
        getUnloader()->unloadChildren( tilesWithChildrenToUnload );
    }
}
osgEarthDrivers/engine_rex/TileNodeRegistry.cpp
void
TileNodeRegistry::run( const TileNodeRegistry::ConstOperation& op ) const
{
    Threading::ScopedReadLock lock( _tilesMutex );
    op.operator()( _tiles );
    OE_TEST << LC << _name << ": tiles=" << _tiles.size() << std::endl;
}

掃描器的掃描過程如下:

osgEarthDrivers/engine_rex/EngineContext.cpp
        enum Policy {
            POLICY_FIND_ALL,
            POLICY_FIND_SOME,
            POLICY_FIND_ONE
        };
        void operator()(const TileNodeRegistry::TileNodeMap& tiles) const
        {
            if ( tiles.empty() ) return;
            unsigned f = _stamp->getFrameNumber(), s = tiles.size();

            switch (_policy)
            {
                case POLICY_FIND_ALL:
                {
                    for (TileNodeRegistry::TileNodeMap::const_iterator i = tiles.begin(); i != tiles.end(); ++i)
                    {
                        const TileNode* tile = i->second.tile.get();
                        if (tile->areSubTilesDormant(_stamp))
                            _keys.push_back(i->first);
                    }
                }
                break;

                case POLICY_FIND_ONE:
                {
                    const TileNode* tile = tiles.at(f%s);
                    if (tile->areSubTilesDormant(_stamp))
                    {
                        _keys.push_back(tile->getKey());
                    }
                }
                break;

                default:
                case POLICY_FIND_SOME:
                {
                    for(unsigned i=0; i<4; ++i) {
                        const TileNode* tile = tiles.at((f+i)%s);
                        if ( tile->areSubTilesDormant(_stamp) )
                            _keys.push_back( tile->getKey() );
                    }
                }
            }
        }

掃描策略預設為POLICY_FIND_ALL,掃描的物件即為活躍瓦片暫存器。對於活躍瓦片暫存器中的每一個瓦片,判斷其子瓦片是否處於休眠狀態Dormant,也即判斷最近一次遍歷時的幀號和時間與當前做比較,如果超出一定閾值則認為時休眠狀態。

osgEarthDrivers/engine_rex/TileNode.cpp
bool
TileNode::areSubTilesDormant(const osg::FrameStamp* fs) const
{
    return
        getNumChildren() >= 4           &&
        getSubTile(0)->isDormant( fs )  &&
        getSubTile(1)->isDormant( fs )  &&
        getSubTile(2)->isDormant( fs )  &&
        getSubTile(3)->isDormant( fs );
}

bool
TileNode::isDormant(const osg::FrameStamp* fs) const
{
    const unsigned minMinExpiryFrames = 3u;

    bool dormant = 
           fs &&
           fs->getFrameNumber() - _lastTraversalFrame > std::max(_minExpiryFrames, minMinExpiryFrames) &&
           fs->getReferenceTime() - _lastTraversalTime > _minExpiryTime;
    return dormant;
}

那麼最近一次遍歷的幀號和時間是怎麼設定呢(在渲染遍歷裡),怎麼就不會再渲染遍歷該瓦片節點了(是不是會根據瓦片距離或畫素等因為來判斷,應該是上部有限定條件)

osgEarthDrivers/engine_rex/TileNode.cpp
bool
TileNode::accept_cull(TerrainCuller* culler)
{
    bool visible = false;
    
    if (culler)
    {
        // update the timestamp so this tile doesn't become dormant.
        _lastTraversalFrame.exchange( culler->getFrameStamp()->getFrameNumber() );
        _lastTraversalTime = culler->getFrameStamp()->getReferenceTime();

        if ( !culler->isCulled(*this) )
        {
            visible = cull( culler );
        }
    }

    return visible;
}

2、解除安裝器更新遍歷時從活躍瓦片暫存器中刪除瓦片節點

osgEarthDrivers/engine_rex/Unloader.cpp
void
UnloaderGroup::traverse(osg::NodeVisitor& nv)
{
    if ( nv.getVisitorType() == nv.UPDATE_VISITOR )
    {        
        if ( _parentKeys.size() > _threshold )
        {
            ScopedMetric m("Unloader expire");

            unsigned unloaded=0, notFound=0, notDormant=0;
            Threading::ScopedMutexLock lock( _mutex );
            for(std::set<TileKey>::const_iterator parentKey = _parentKeys.begin(); parentKey != _parentKeys.end(); ++parentKey)
            {
                osg::ref_ptr<TileNode> parentNode;
                if ( _tiles->get(*parentKey, parentNode) )
                {
                    // re-check for dormancy in case something has changed
                    if ( parentNode->areSubTilesDormant(nv.getFrameStamp()) )
                    {
                        // find and move all tiles to be unloaded to the dead pile.
                        ExpirationCollector collector( _tiles );
                        for(unsigned i=0; i<parentNode->getNumChildren(); ++i)
                            parentNode->getSubTile(i)->accept( collector );
                        unloaded += collector._count;

                        // submit all collected nodes for GL resource release:
                        if (!collector._nodes.empty() && _releaser.valid())
                            _releaser->push(collector._nodes);

                        parentNode->removeSubTiles();
                    }
                    else notDormant++;
                }
                else notFound++;
            }

            OE_DEBUG << LC << "Total=" << _parentKeys.size() << "; threshold=" << _threshold << "; unloaded=" << unloaded << "; notDormant=" << notDormant << "; notFound=" << notFound << "\n";
            _parentKeys.clear();
        }
    }
    osg::Group::traverse( nv );
}

當需要解除安裝的瓦片節點超過一定閾值後(預設為INT_MAX),對要刪除的每一個瓦片節點執行如下操作:

(1)判斷該瓦片的所有子瓦片是否處於休眠狀態Dormant,如果是從活躍瓦片暫存器中移除這些子瓦片(通過訪問器ExpirationCollector來實現移除操作)。

osgEarthDrivers/engine_rex/Unloader.cpp
void apply(osg::Node& node)
        {
            // Make sure the tile is still dormat before releasing it
            TileNode* tn = dynamic_cast<TileNode*>( &node );
            if ( tn )
            {
                _nodes.push_back(tn);
                _tiles->remove( tn );
                _count++;
            }
            traverse(node);
        }

(2)對要移除的瓦片節點,放入資源釋放器(本質是osg::Drawable物件),釋放分配的opengl資源。並不是立即釋放,而是要等到osgUtil::SceneView::draw時最終呼叫資源釋放器的drawImplementation時進行釋放,該draw不一定在幀迴圈中呼叫。資源釋放器通過引用指標的方式管理要釋放的瓦片節點,因此(3)中的移除不會真正從記憶體中移除,只有資源釋放器移除後才會真正從記憶體中移除。

osgEarth/ResourceReleaser.cpp
void
ResourceReleaser::releaseGLObjects(osg::State* state) const
{
    osg::Drawable::releaseGLObjects(state);

    if (!_toRelease.empty())
    {
        Threading::ScopedMutexLock lock(_mutex);
        if (!_toRelease.empty())
        {
            METRIC_SCOPED("ResourceReleaser");
            for (ObjectList::const_iterator i = _toRelease.begin(); i != _toRelease.end(); ++i)
            {
                osg::Object* object = i->get();
                object->releaseGLObjects(state);
            }
            OE_DEBUG << LC << "Released " << _toRelease.size() << " objects\n";
            _toRelease.clear();
        }
    }
}
osgEarthDrivers/engine_rex/TileNode.cpp
void
TileNode::releaseGLObjects(osg::State* state) const
{
    osg::Group::releaseGLObjects(state);

    if ( _surface.valid() )
        _surface->releaseGLObjects(state);

    if ( _patch.valid() )
        _patch->releaseGLObjects(state);

    _renderModel.releaseGLObjects(state);
}

(3)從場景樹中移除子瓦片節點,從而保持了場景中瓦片和活躍瓦片暫存器中的瓦片是一致的。

 

具體參看(三十三) ,在更新遍歷分頁瓦片解除安裝器時,達到一定條件會對瓦片暫存器中的瓦片進行移除,但是瓦片所在的記憶體還在,只是不再有瓦片暫存器來管理了。TileNodeRegistry::removeSafely(const TileKey& key)

RexTerrainEngineNode的遍歷過程詳解

osgEarth::Drivers::RexTerrainEngine::TerrainCuller的apply過程詳解

RexTerrainEngineNode的updateState過程詳解 設定了很多著色器變數

什麼時候分配opengl資源

TileNode釋放opengl資源過程releaseGLObjects詳解

最近一次遍歷的幀號和時間是怎麼設定呢(在渲染遍歷裡),怎麼就不會再渲染遍歷該瓦片節點了