目标:(十二)中的问题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详解
最近一次遍历的帧号和时间是怎么设置呢(在渲染遍历里),怎么就不会再渲染遍历该瓦片节点了