31#include <osgViewer/Viewer>
32#include <osgDB/Registry>
34#include <simgear/constants.h>
35#include <simgear/debug/logstream.hxx>
36#include <simgear/structure/exception.hxx>
37#include <simgear/scene/model/modellib.hxx>
38#include <simgear/scene/util/SGReaderWriterOptions.hxx>
39#include <simgear/scene/tgdb/VPBTechnique.hxx>
40#include <simgear/scene/tgdb/VPBLineFeatureRenderer.hxx>
41#include <simgear/scene/tsync/terrasync.hxx>
42#include <simgear/misc/strutils.hxx>
43#include <simgear/scene/material/matlib.hxx>
62#include <simgear/io/torrent.hxx>
64static bool s_torrentRuntimeEnabled =
false;
65static std::mutex s_torrentMutex;
66static std::vector<std::string> s_torrentScenerySuffixes;
71 std::shared_ptr<libtorrent::torrent_info> torrent_info;
72 bool finished =
false;
76static std::map<std::string, torrentStatus> s_torrentDirToStatus;
78static std::ostream&
operator<<(std::ostream& out,
const torrentStatus&
status)
80 return out <<
"torrentStatus {"
81 <<
" finished=" <<
status.finished
86static void torrentScheduleTileCallback(
const std::string& dir,
bool ok)
88 SG_LOG(SG_TERRAIN, SG_ALERT,
"torrentScheduleTileCallback(): dir=" << dir <<
" ok=" << ok);
89 std::unique_lock lock(s_torrentMutex);
91 assert(!s_torrentDirToStatus[dir].finished);
93 s_torrentDirToStatus[dir].torrent_info =
nullptr;
94 s_torrentDirToStatus[dir].finished =
true;
95 s_torrentDirToStatus[dir].ok = ok;
98static bool torrentIsSyncing(
const std::string& path)
100 SG_LOG( SG_TERRAIN, SG_DEBUG,
"torrentIsSyncing(): path=" << path);
101 std::unique_lock lock(s_torrentMutex);
104 for (
auto it: s_torrentDirToStatus)
106 SG_LOG( SG_TERRAIN, SG_DEBUG,
"torrentIsSyncing(): it.first=" << it.first);
107 const torrentStatus&
status = it.second;
114 const libtorrent::file_storage& file_storage =
status.torrent_info->files();
115 for (
int i=0;
i<file_storage.num_files(); ++
i)
117 SG_LOG( SG_TERRAIN, SG_DEBUG,
"torrentIsSyncing():"
118 " file_storage.file_name(i)=" << file_storage.file_name(
i)
120 if (file_storage.file_name(
i) == path)
122 SG_LOG(SG_TERRAIN, SG_DEBUG,
"torrentIsSyncing():"
136 SG_LOG(SG_TERRAIN, SG_DEBUG,
"torrentIsSyncing():"
137 <<
" returning false"
143static void torrentInfoCallback(
const std::string& dir, std::shared_ptr<libtorrent::torrent_info> torrent_info)
145 std::unique_lock lock(s_torrentMutex);
146 SG_LOG(SG_TERRAIN, SG_DEBUG,
"torrentInfoCallback():" <<
" dir=" << dir);
147 assert(!s_torrentDirToStatus[dir].torrent_info);
148 s_torrentDirToStatus[dir].torrent_info = torrent_info;
151static void torrentScheduleTile(
const SGBucket& bucket)
153 std::string basePath = bucket.gen_base_path();
154 SG_LOG( SG_TERRAIN, SG_DEBUG,
"torrentScheduleTile(): basePath=" << basePath);
155 assert(!s_torrentScenerySuffixes.empty());
156 std::string url_base =
"http://us1mirror.flightgear.org/terrasync/ws2";
157 for (
const std::string& scenerySuffix : s_torrentScenerySuffixes)
159 std::string dir = scenerySuffix +
"/" + basePath;
160 size_t p = dir.rfind(
"/");
161 assert(
p != std::string::npos);
162 std::string dir_parent = dir.substr(0,
p);
163 std::string torrent_url = url_base +
"/" + dir +
".torrent";
164 SG_LOG( SG_TERRAIN, SG_DEBUG,
"torrentScheduleTile():"
165 <<
" basePath=" << basePath
166 <<
" scenerySuffix=" << scenerySuffix
167 <<
" torrent_url=" << torrent_url
169 std::unique_lock lock(s_torrentMutex);
170 auto it = s_torrentDirToStatus.find(dir);
171 if (it == s_torrentDirToStatus.end())
173 s_torrentDirToStatus[dir] = torrentStatus();
174 simgear::Torrent* torrent =
globals->get_subsystem<simgear::Torrent>();
175 SGPath scenery_dir =
fgGetString(
"/sim/terrasync/scenery-dir");
176 SG_LOG( SG_TERRAIN, SG_DEBUG,
"/sim/terrasync/scenery-dir=" << scenery_dir);
177 assert(scenery_dir.str() !=
"");
181 SGPath torrent_path = scenery_dir / (dir +
".torrent");
182 SGPath out_path = scenery_dir / dir_parent;
183 torrent->add_torrent_url(
187 std::bind(torrentScheduleTileCallback, dir, std::placeholders::_1),
188 std::bind(torrentInfoCallback, dir, std::placeholders::_1)
194 SG_LOG( SG_TERRAIN, SG_DEBUG,
"torrentScheduleTile(): not downloading because already trying/tried:"
196 <<
" it->second=" << it->second
210 _useVBOsProp(
fgGetNode(
"/sim/rendering/use-vbos", true)),
211 _enableCacheProp(
fgGetNode(
"/sim/tile-cache/enable", true)),
212 _pagedLODMaximumProp(
fgGetNode(
"/sim/rendering/max-paged-lod", true)),
213 _lodDetailed(
fgGetNode(
"/sim/rendering/static-lod/detailed", true)),
214 _lodRoughDelta(
fgGetNode(
"/sim/rendering/static-lod/rough-delta", true)),
215 _lodBareDelta(
fgGetNode(
"/sim/rendering/static-lod/bare-delta", true)),
216 _lodRough(
fgGetNode(
"/sim/rendering/static-lod/rough", true)),
217 _lodBare(
fgGetNode(
"/sim/rendering/static-lod/bare", true))
219 _useVBOsProp->addChangeListener(
this,
true);
221 _enableCacheProp->addChangeListener(
this,
true);
222 if (_enableCacheProp->getType() == simgear::props::NONE) {
223 _enableCacheProp->setBoolValue(true);
226 if (_pagedLODMaximumProp->getType() == simgear::props::NONE) {
228 osg::ref_ptr<osgViewer::View> view(globals->get_renderer()->getView());
229 int current = view->getDatabasePager()->getTargetMaximumNumberOfPageLOD();
230 _pagedLODMaximumProp->setIntValue(current);
232 _pagedLODMaximumProp->addChangeListener(
this,
true);
233 _lodDetailed->addChangeListener(
this,
true);
234 _lodBareDelta->addChangeListener(
this,
true);
235 _lodRoughDelta->addChangeListener(
this,
true);
240 _useVBOsProp->removeChangeListener(
this);
241 _enableCacheProp->removeChangeListener(
this);
242 _pagedLODMaximumProp->removeChangeListener(
this);
243 _lodDetailed->removeChangeListener(
this);
244 _lodBareDelta->removeChangeListener(
this);
245 _lodRoughDelta->removeChangeListener(
this);
250 if (prop == _useVBOsProp) {
251 bool useVBOs = prop->getBoolValue();
252 _manager->_options->setPluginStringData(
"SimGear::USE_VBOS",
253 useVBOs ?
"ON" :
"OFF");
254 }
else if (prop == _enableCacheProp) {
255 _manager->_enableCache = prop->getBoolValue();
256 }
else if (prop == _pagedLODMaximumProp) {
257 int v = prop->getIntValue();
258 osg::ref_ptr<osgViewer::View> view(
globals->get_renderer()->getView());
260 osgDB::DatabasePager*
pager = view->getDatabasePager();
261 if (
pager)
pager->setTargetMaximumNumberOfPageLOD(v);
263 }
else if (prop == _lodDetailed || prop == _lodBareDelta || prop == _lodRoughDelta) {
268 _lodRough->setDoubleValue(_lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue());
269 _lodBare->setDoubleValue(_lodRough->getDoubleValue() + _lodBareDelta->getDoubleValue());
273 prop->getStringValue(),
279 SGPropertyNode_ptr _useVBOsProp,
281 _pagedLODMaximumProp,
292 last_state( Running ),
293 scheduled_visibility(100.0),
294 _visibilityMeters(
fgGetNode(
"/environment/visibility-m", true)),
295 _lodDetailed(
fgGetNode(
"/sim/rendering/static-lod/detailed", true)),
296 _lodRoughDelta(
fgGetNode(
"/sim/rendering/static-lod/rough-delta", true)),
297 _lodBareDelta(
fgGetNode(
"/sim/rendering/static-lod/bare-delta", true)),
298 _disableNasalHooks(
fgGetNode(
"/sim/temp/disable-scenery-nasal", true)),
299 _scenery_loaded(
fgGetNode(
"/sim/sceneryloaded", true)),
300 _scenery_override(
fgGetNode(
"/sim/sceneryloaded-override", true)),
301 _pager_file_queue_size(
fgGetNode(
"/sim/rendering/statistics/database-pager/file-queue-size", true)),
302 _pager_compile_queue_size(
fgGetNode(
"/sim/rendering/statistics/database-pager/compile-queue-size", true)),
303 _pager_merge_queue_size(
fgGetNode(
"/sim/rendering/statistics/database-pager/merge-queue-size", true)),
304 _pager_min_merge_time(
fgGetNode(
"/sim/rendering/statistics/database-pager/min-merge-time", true)),
305 _pager_mean_merge_time(
fgGetNode(
"/sim/rendering/statistics/database-pager/mean-merge-time", true)),
306 _pager_max_merge_time(
fgGetNode(
"/sim/rendering/statistics/database-pager/max-merge-time", true)),
307 _pager_active_lod_count(
fgGetNode(
"/sim/rendering/statistics/database-pager/active-paged-lod-count", true)),
312 const char* torrent_enabled_path =
"/sim/torrent/enabled";
313 SGPropertyNode* torrent_enabled_node =
fgGetNode(torrent_enabled_path);
314 if (torrent_enabled_node)
317 s_torrentRuntimeEnabled = torrent_enabled_node->getBoolValue();
319 SG_LOG(SG_TERRAIN, SG_ALERT,
320 "This Flightgear build does not support torrents;"
321 " ignoring property "
322 << torrent_enabled_path <<
"='" << torrent_enabled_node->getStringValue() <<
"'"
346 group->removeChildren(0, group->getNumChildren());
349 osgDB::Registry::instance()->clearObjectCache();
355 SG_LOG( SG_TERRAIN, SG_INFO,
"Initializing Tile Manager subsystem." );
356 auto terraSync =
globals->get_subsystem<simgear::SGTerraSync>();
359 _options =
new simgear::SGReaderWriterOptions;
363 _options->setPropertyNode(
globals->get_props());
365 osgDB::FilePathList &fp = _options->getDatabasePathList();
368 for (
auto it = sc.begin(); it != sc.end(); ++it) {
369 fp.push_back(it->utf8Str());
371 _options->setPluginStringData(
"SimGear::FG_ROOT",
globals->get_fg_root().utf8Str());
374 _options->setPluginStringData(
"SimGear::TERRASYNC_ROOT",
globals->get_terrasync_dir().utf8Str());
377 if (!_disableNasalHooks->getBoolValue())
380 double detailed =
fgGetDouble(
"/sim/rendering/static-lod/detailed", SG_OBJECT_RANGE_DETAILED);
381 double rough =
fgGetDouble(
"/sim/rendering/static-lod/rough-delta", SG_OBJECT_RANGE_ROUGH) + detailed;
382 double bare =
fgGetDouble(
"/sim/rendering/static-lod/bare", SG_OBJECT_RANGE_BARE) + rough;
383 double tile_min_expiry =
fgGetDouble(
"/sim/rendering/plod-minimum-expiry-time-secs", SG_TILE_MIN_EXPIRY);
386 _use_vpb =
fgGetBool(
"/scenery/use-vpb");
388 _options->setPluginStringData(
"SimGear::LOD_RANGE_BARE", std::to_string(bare));
389 _options->setPluginStringData(
"SimGear::LOD_RANGE_ROUGH", std::to_string(rough));
390 _options->setPluginStringData(
"SimGear::LOD_RANGE_DETAILED", std::to_string(detailed));
391 _options->setPluginStringData(
"SimGear::PAGED_LOD_EXPIRY", std::to_string(tile_min_expiry));
394 for (
auto node :
fgGetNode(
"/sim/rendering/",
true)->getChildren(
"scenery-path-suffix")) {
395 if (node->getBoolValue(
"enabled",
true)) {
396 scenerySuffixes.push_back(node->getStringValue(
"name"));
400 if (scenerySuffixes.empty()) {
402 scenerySuffixes = {
"Objects",
"Terrain"};
406 s_torrentScenerySuffixes = scenerySuffixes;
409 terraSync->setSceneryPathSuffixes(scenerySuffixes);
411 _options->setSceneryPathSuffixes(scenerySuffixes);
417 if (_scenery_loaded->getBoolValue() ==
false) {
418 SG_LOG( SG_TERRAIN, SG_INFO,
"/sim/sceneryloaded already false, avoiding duplicate re-init of tile manager" );
423 _scenery_loaded->setBoolValue(
false);
429 osg::Group* group =
globals->get_scenery()->get_terrain_branch();
430 group->removeChildren(0, group->getNumChildren());
436 osgDB::Registry::instance()->clearObjectCache();
441 previous_bucket.make_bad();
442 current_bucket.make_bad();
443 scheduled_visibility = 100.0;
451 _options->setMaterialLib(
globals->get_matlib());
456bool FGTileMgr::sched_tile(
const SGBucket& b,
double priority,
bool current_view,
double duration)
464 SG_LOG( SG_TERRAIN, SG_INFO,
"sched_tile: new STG tile entry for:" << b );
480 SG_LOG( SG_TERRAIN, SG_DEBUG,
" New tile cache size " << (
int)tile_cache.get_size() );
484 tile_cache.request_tile(t,priority,current_view,duration);
487 VPBTileEntry *v = tile_cache.get_vpb_tile( b );
492 v =
new VPBTileEntry( b, _options );
493 SG_LOG( SG_TERRAIN, SG_INFO,
"sched_tile: new VPB tile entry for:" << b );
496 if ( tile_cache.insert_tile( v ) )
507 SG_LOG( SG_TERRAIN, SG_DEBUG,
" New tile cache size " << (
int)tile_cache.get_size() );
512 tile_cache.request_tile(v, 1.0, current_view, duration);
520void FGTileMgr::schedule_needed(
const SGBucket& curr_bucket,
double vis)
523 if (!curr_bucket.isValid() )
525 SG_LOG( SG_TERRAIN, SG_ALERT,
526 "Attempting to schedule tiles for invalid bucket" );
530 double tile_width = curr_bucket.get_width_m();
531 double tile_height = curr_bucket.get_height_m();
532 SG_LOG( SG_TERRAIN, SG_INFO,
533 "scheduling needed tiles for " << curr_bucket
534 <<
", tile-width-m:" << tile_width <<
", tile-height-m:" << tile_height);
541 double maxTileRange = _lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue() + _lodBareDelta->getDoubleValue();
543 double tileRangeM = std::min(vis, maxTileRange);
544 int xrange = (int)(tileRangeM / tile_width) + 1;
545 int yrange = (int)(tileRangeM / tile_height) + 1;
546 if ( xrange < 1 ) { xrange = 1; }
547 if ( yrange < 1 ) { yrange = 1; }
551 tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
557 tile_cache.clear_current_view();
560 osg::FrameStamp* framestamp
562 tile_cache.set_current_time(framestamp->getReferenceTime());
567 auto terraSync =
globals->get_subsystem<simgear::SGTerraSync>();
571 SGGeod centerPos = curr_bucket.get_center();
573 for ( x = -xrange; x <= xrange; ++x )
575 for ( y = -yrange; y <= yrange; ++y )
577 SGBucket b = curr_bucket.sibling(x, y);
578 SGGeod bPos = b.get_center();
580 double d = SGGeodesy::distanceM(centerPos, bPos);
583 double priority = (2.0 * tileRangeM - d) / (2.0 * tileRangeM);
584 SG_LOG(SG_TERRAIN, SG_DEBUG,
" Scheduling Tile STG file " << b.get_center_lat() <<
", " << b.get_center_lon() <<
" distance " << d <<
" priority: " << priority);
585 sched_tile( b, priority,
true, 0.0 );
588 if (s_torrentRuntimeEnabled) {
589 torrentScheduleTile(b);
594 terraSync->scheduleTile(b);
604void FGTileMgr::update_queues(
bool& isDownloadingScenery)
607 double current_time = framestamp->getReferenceTime();
608 double vis = _visibilityMeters->getDoubleValue();
613 tile_cache.set_current_time( current_time );
614 tile_cache.reset_traversal();
616 while ( ! tile_cache.at_end() )
618 e = tile_cache.get_current();
629 isDownloadingScenery |= downloading;
630 if ( !downloading && nonExpiredOrCurrent) {
642 SG_LOG(SG_TERRAIN, SG_ALERT,
"Warning: empty tile in cache!");
648 int drop_count = sz - tile_cache.get_max_cache_size();
649 bool dropTiles =
false;
651 dropTiles = ( drop_count > 0 ) && ((loading==0)||(drop_count > 10));
659 long drop_index = _enableCache ? tile_cache.get_drop_tile() :
660 tile_cache.get_first_expired_tile();
661 while ( drop_index > -1 )
664 TileEntry* old = tile_cache.get_tile(drop_index);
667 tile_cache.clear_entry(drop_index);
671 simgear::VPBLineFeatureRenderer::unloadFeatures(old->
get_tile_bucket());
674 osg::ref_ptr<osg::Object> subgraph = old->
getNode();
679 _pager->queueDeleteRequest(subgraph);
682 drop_index = tile_cache.get_first_expired_tile();
684 else if (--drop_count > 0)
685 drop_index = tile_cache.get_drop_tile();
697 double vis = _visibilityMeters->getDoubleValue();
698 schedule_tiles_at(
globals->get_view_position(), vis);
700 bool waitingOnTerrasync =
false;
701 update_queues(waitingOnTerrasync);
705 _pager_file_queue_size->setIntValue(_pager->getFileRequestListSize());
706 _pager_compile_queue_size->setIntValue(_pager->getDataToCompileListSize());
707 _pager_merge_queue_size->setIntValue(_pager->getDataToMergeListSize());
708 _pager_active_lod_count->setIntValue(_pager->getActivePagedLODCount());
709 _pager_min_merge_time->setFloatValue(_pager->getMinimumTimeToMergeTile());
710 _pager_mean_merge_time->setFloatValue(_pager->getAverageTimeToMergeTiles());
711 _pager_max_merge_time->setFloatValue(_pager->getMaximumTimeToMergeTile());
715 if (!_scenery_loaded->getBoolValue())
717 bool fdmInited =
fgGetBool(
"sim/fdm-initialized");
718 bool positionFinalized =
fgGetBool(
"sim/position-finalized");
719 bool sceneryOverride = _scenery_override->getBoolValue();
724 if (positionFinalized && (sceneryOverride || (
isSceneryLoaded() && fdmInited)))
726 _scenery_loaded->setBoolValue(
true);
731 if (!positionFinalized) {
733 }
else if (waitingOnTerrasync) {
740 SGTimeStamp::sleepForMSec(50);
747void FGTileMgr::schedule_tiles_at(
const SGGeod& location,
double range_m)
752 current_bucket = SGBucket( location );
756 if (range_m - scheduled_visibility > 5000.0)
757 previous_bucket.make_bad();
761 fgSetInt(
"/environment/current-tile-id", current_bucket.gen_index() );
765 if ( state == Running ) {
766 if (last_state != state)
768 SG_LOG( SG_TERRAIN, SG_DEBUG,
"State == Running" );
770 if (current_bucket != previous_bucket) {
773 SG_LOG( SG_TERRAIN, SG_INFO,
"FGTileMgr: at " << location <<
", scheduling needed for:" << current_bucket
774 <<
", visibility=" << range_m);
775 scheduled_visibility = range_m;
776 schedule_needed(current_bucket, range_m);
780 previous_bucket = current_bucket;
781 }
else if ( state == Start || state == Inited ) {
782 SG_LOG( SG_TERRAIN, SG_DEBUG,
"State == Start || Inited" );
785 previous_bucket.make_bad();
798 if (!position.isValid())
801 bool available =
true;
803 SGBucket bucket(position);
804 available = sched_tile( bucket, 1.0,
false, duration);
806 if ((!available)&&(duration==0.0)) {
807 SG_LOG( SG_TERRAIN, SG_DEBUG,
"schedule_scenery: Scheduling tile at bucket:" << bucket <<
" return false" );
811 SGVec3d cartPos = SGVec3d::fromGeod(position);
814 double tile_width = bucket.get_width_m();
815 double tile_height = bucket.get_height_m();
816 double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
817 double max_dist = tile_r + range_m;
818 double max_dist2 = max_dist*max_dist;
820 int xrange = (int)fabs(range_m / tile_width) + 1;
821 int yrange = (int)fabs(range_m / tile_height) + 1;
823 for (
int x = -xrange; x <= xrange; ++x )
825 for (
int y = -yrange; y <= yrange; ++y )
828 if ( x != 0 || y != 0 )
830 SGBucket b = bucket.sibling(x, y );
835 double distance2 = distSqr(cartPos, SGVec3d::fromGeod(b.get_center()));
837 if (distance2 <= max_dist2)
839 float priority = (max_dist2 - distance2) / max_dist2;
840 available &= sched_tile( b, priority,
false, duration );
841 if ((!available)&&(duration==0.0))
854 double range_m = 100.0;
855 if (scheduled_visibility < range_m)
856 range_m = scheduled_visibility;
861bool FGTileMgr::isTileDirSyncing(
const std::string& tileFileName)
const
863 auto terraSync =
globals->get_subsystem<simgear::SGTerraSync>();
870 if (terraSync->isDataDirPending(
"Models")) {
875 if (s_torrentRuntimeEnabled) {
876 return torrentIsSyncing(tileFileName);
879 std::string nameWithoutExtension = tileFileName.substr(0, tileFileName.size() - 4);
880 long int bucketIndex = simgear::strutils::to_int(nameWithoutExtension);
881 SGBucket bucket(bucketIndex);
883 return terraSync->isTileDirPending(bucket.gen_base_path());
virtual FGRenderer * get_renderer() const
FGScenery * get_scenery() const
Thread-safe proxy for FGNasalModelData.
osg::FrameStamp * getFrameStamp() const
osg::Group * get_terrain_branch() const
TileManagerListener(FGTileMgr *manager)
virtual void valueChanged(SGPropertyNode *prop)
bool schedule_scenery(const SGGeod &position, double range_m, double duration=0.0)
Schedules scenery for given position.
void materialLibChanged()
friend class TileManagerListener
STGTileEntry * get_stg_tile(const SGBucket &b) const
bool insert_tile(STGTileEntry *e)
Create a new tile and enqueue it for loading.
void prep_ssg_node(float vis)
bool is_expired(double current_time) const
Return false if the tile entry is still needed, otherwise return true indicating that the tile is no ...
float get_priority() const
osg::ref_ptr< osg::Referenced > & getDatabaseRequest()
void addToSceneGraph(osg::Group *terrain_branch)
Add terrain mesh and ground lighting to scene graph.
bool is_current_view() const
const SGBucket & get_tile_bucket() const
Return the "bucket" for this tile.
bool is_loaded() const
Return true if the tile entry is loaded, otherwise return false indicating that the loading thread is...
void removeFromSceneGraph()
disconnect terrain mesh and ground lighting nodes from scene graph for this tile.
osg::LOD * getNode() const
return the scenegraph node for the terrain
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
bool fgSetInt(const char *name, int val)
Set an int value for a property.
std::vector< SGPath > PathList
std::vector< std::string > string_list
void addSentryBreadcrumb(const std::string &, const std::string &)
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
std::ostream & operator<<(std::ostream &out, const FGFrameInfo &frame_info)
static osg::ref_ptr< SceneryPager > pager
void fgSplashProgress(const char *identifier, unsigned int percent)
Set progress information.