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详解

最近一次遍历的帧号和时间是怎么设置呢(在渲染遍历里),怎么就不会再渲染遍历该瓦片节点了

猜你喜欢

转载自blog.csdn.net/hankern/article/details/85407803
今日推荐