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詳解
最近一次遍歷的幀號和時間是怎麼設定呢(在渲染遍歷裡),怎麼就不會再渲染遍歷該瓦片節點了