osgEarth的Rex引擎原理分析(十七)瓦片請求的生成到處理過程詳解
目標:(十六)中問題38
瓦片請求的大概過程是這樣的:
1、osgEarth::Drivers::RexTerrainEngine::TileNode在渲染遍歷時產生LoadTileData請求,將請求傳遞給DatabsePager改造成DatabaseRequest請求後,將此請求放入了DatabasePager的_fileRequestQueue佇列中
2、DatabasePager的執行執行緒DatabaseThread又將DatabaseRequest請求部分屬性進行設定後,放入DatabasePager的_dataToMergeList的佇列中
3、在更新遍歷DatabasePager時,將其_dataToMergeList列表中的DatabaseRequest請求經過轉換成LoadTileData請求後,放入瓦片分頁載入器的_mergeQueue佇列中
4、在更新遍歷時瓦片分頁載入器處理_mergeQueue中的請求
下面圍繞著每個過程中的子過程有哪些,請求是如何在其中運轉騰挪的這麼一個思路來詳細展開。
1、osgEarth::Drivers::RexTerrainEngine::TileNode在渲染遍歷時產生請求
(1)在渲染遍歷時,TileNode通過PagerLoader載入請求
osgEarthDrivers/engine_rex/TileNode.cpp void TileNode::load(TerrainCuller* culler) { _context->getLoader()->load( _loadRequest.get(), priority, *culler ); }
這裡的_context為rex引擎RexTerrainEngineNode中設定的(見(十二)),在TileNode執行create的時候賦值。getLoader就是獲取分頁瓦片載入器PagerLoader,然後由PagerLoader執行load方法載入請求。
這裡的_loadRequest為LoadTileData物件,是TileNode的成員變數,在TileNode執行create的時候賦值。從程式碼可以看出,建立瓦片節點時,相應的高程和影像資料並沒有同時載入進來,而是通過多執行緒處理請求的方式來載入。
osgEarthDrivers/engine_rex/TileNode.cpp void TileNode::create(const TileKey& key, TileNode* parent, EngineContext* context) { _context = context; _loadRequest = new LoadTileData( this, context ); _loadRequest->setName( _key.str() ); _loadRequest->setTileKey( _key ); }
這裡的priority意思為載入瓦片的優先順序
這裡的culler為裁剪遍歷器
(2)PagerLoader再通過DatabasePager處理請求
osgEarthDrivers/engine_rex/Loader.cpp
bool
PagerLoader::load(Loader::Request* request, float priority, osg::NodeVisitor& nv)
{
request->setState(Request::RUNNING);
// remember the last tick at which this request was submitted
request->_lastTick = osg::Timer::instance()->tick();
// update the priority, scale and bias it, and then normalize it to [0..1] range.
unsigned lod = request->getTileKey().getLOD();
float p = priority * _priorityScales[lod] + _priorityOffsets[lod];
request->_priority = p / (float)(_numLODs+1);
request->setFrameNumber( fn );
request->_loadCount++;
char filename[64];
sprintf(filename, "%u.osgearth_rex_loader", request->_uid);
nv.getDatabaseRequestHandler()->requestNodeFile(
filename,
_myNodePath,
request->_priority,
nv.getFrameStamp(),
request->_internalHandle,
_dboptions.get() );
_requests[request->getUID()] = request;
}
PagerLoader先對請求的相關屬性進行設定,然後在轉交給DatabasePager做進一步處理,包括設定請求的狀態(有空閒、執行、合併、完成四種狀態)、請求提交的時間、請求的優先順序、請求的幀號、。
這裡的filename,request的_uid為請求LoadTileData構造時,由其父類Loader::Request在建構函式時通過osgEarth的Registry獲得的。filename如何一步步傳遞變現,需要在下文持續跟蹤。
osgEarth/Registry.cpp
UID
Registry::createUID()
{
//todo: use OpenThreads::Atomic for this
ScopedLock<Mutex> exclusive( _uidGenMutex );
return (UID)( _uidGen++ );
}
_myNodePath存放的是節點路徑,其實這裡面只有一個節點,就是分頁瓦片載入器PagerLoader,PagerLoader在建構函式時將自身指標放入了該路徑裡。
request的_priority為請求的優先順序,這個優先順序在PagerLoader的load函式做過處理,但是為什麼要如此處理,需關注問題40的跟蹤(十九)。
request的_internalHandle,需關注問題41的跟蹤,第一次這個指標內容是空的,在DatabasePager的requestNodeFile函式中會對這個指標賦值,最終該指標指向的是DatabaseRequest請求。
_dboptions設定資料庫相關選項,包括檔案位置回撥函式和分頁瓦片載入器(後面的處理過程中,會從這個選項中取出這個PagerLoader),在PagerLoader的建構函式中設定。
PagerLoader在對請求處理完後,會將其存放到自己的請求列表_requests中,以備以後使用。
(3)DatabasePager的requestNodeFile對請求做進一步處理
主要分三種情況:
一是第一次處理這種請求,構造DatabaseRequest的成員資訊,包括合法性、檔名、第一次請求的幀號、第一次請求的幀戳、第一次請求的優先順序、最後一次請求的幀號、最後一次請求的幀戳、最後一次請求的優先順序、組節點(其實應該是分頁載入器節點PagerLoader)、地形節點(容納所有瓦片的節點)、載入選項(見上面的_dboptions)、物件快取_objectCache。
二是第二次以上處理這種請求,更新設定DatabaseRequest的部分成員資訊,如合法性、最後一次請求的幀號、最後一次請求的幀戳、最後一次請求的優先順序、請求數量等。
三是第一次處理過然後又刪除過(猜測的,待確認),重新設定DatabaseRequest的成員資訊,如最後一次請求的幀號、最後一次請求的幀戳、最後一次請求的優先順序、組節點(其實應該是分頁載入器節點PagerLoader)、地形節點(容納所有瓦片的節點)、載入選項(見上面的_dboptions)、物件快取_objectCache。
請求處理完成後,會將請求DatabaseRequest放入到_fileRequestQueue佇列中,在DatabaseThread執行緒中進一步處理。
值得注意的是,在這個函式中會判斷DatabaseThread是否執行,如果沒有的話則建立執行緒並允許。
經過這一步處理後,原始的LoadTileData請求轉變成了DatabaseRequest請求,這兩個請求不存在繼承關係,DatabaseRequest是LoadTileData中的一個成員指標_internalHandle。這樣對DatabaseRequest的處理就變成了對LoadTileData的處理。
2、DatabaseThread執行緒對_fileRequestQueue佇列中的請求進行處理
執行緒的主要處理結果是設定DatabaseRequest的_loadedModel成員變數。然後將請求放入DatabasePager的_dataToMergeList的佇列中。
每次從佇列中取出第一個請求,按如下流程處理:
(1)請求資料在快取中存在,從緩衝中讀,專列文章分析
(2)快取中不存在,從檔案中讀
osgEarth通過外掛載入的方式(詳見文章(三)),最後由osgEarth::Drivers::RexTerrainEngine::PagerLoaderAngent的readNode方法負責對請求進一步處理。readNode具體是由PagerLoader的invokeAndRelease對請求處理完後返回請求,然後PagerLoaderAngent將請求封裝到RequestResultNode(繼承自osg::Node,裡面有包含請求資訊的成員變數_request,瓦片模型資訊在請求裡)中返回到ReadResult中供使用,而invokeAndRelease具體是由請求(本質上是LoadTileData)呼叫自己的invoke方法完善自己的_dataModel成員變數。_dataModel是通過rex引擎的createTileModel來建立的,具體建立方法如下:
osgEarth/TerrainEngineNode.cpp
TerrainTileModel*
TerrainEngineNode::createTileModel(const MapFrame& frame,
const TileKey& key,
const CreateTileModelFilter& filter,
ProgressCallback* progress
)
{
TerrainEngineRequirements* requirements = this;
// Ask the factory to create a new tile model:
osg::ref_ptr<TerrainTileModel> model = _tileModelFactory->createTileModel(
frame,
key,
filter,
requirements,
progress);
}
由於TerrainEngineNode是多繼承的,通過TerrainEngineRequirements* requirements = this;進行型別明確。
這裡的_tileModelFactory為rex引擎在setMap時建立的地形瓦片模型工廠osgEarth::TerrainTileModelFactory,用於建立地形瓦片模型等。具體建立過程為:
osgEarth/TerrainTileModelFactory.cpp
TerrainTileModel*
TerrainTileModelFactory::createTileModel(const MapFrame& frame,
const TileKey& key,
const CreateTileModelFilter& filter,
const TerrainEngineRequirements* requirements,
ProgressCallback* progress)
{
// Make a new model:
osg::ref_ptr<TerrainTileModel> model = new TerrainTileModel(
key,
frame.getRevision() );
// assemble all the components:
//addImageLayers(model.get(), frame, requirements, key, filter, progress);
addColorLayers(model.get(), frame, requirements, key, filter, progress);
addPatchLayers(model.get(), frame, key, filter, progress);
if ( requirements == 0L || requirements->elevationTexturesRequired() )
{
unsigned border = requirements->elevationBorderRequired() ? 1u : 0u;
addElevation( model.get(), frame, key, filter, border, progress );
}
#if 0
if ( requirements == 0L || requirements->normalTexturesRequired() )
{
addNormalMap( model.get(), frame, key, progress );
}
#endif
// done.
return model.release();
}
生成好DatabaseRequest請求中的_loadedModel後,請求會被放入到DatabasePager的_dataToMergeList中。
3、在更新遍歷DatabasePager時
將其_dataToMergeList列表中的DatabaseRequest請求傳給PagerLoader(是由DatabasePager的addLoadedDataToSceneGraph函式來完成的),經過PagerLoader轉換成原先的LoadTileData請求後,放入PagerLoader的_mergeQueue佇列中(是由PagerLoader的addChild完成的)。
4、在更新遍歷時瓦片分頁載入器處理_mergeQueue中的請求
從合併請求佇列中取出第一個請求,總共處理的請求數不超過設定的每幀最多處理的數目(在PagerLoader建立時會設定),對請求進行處理。
總結,請求是在TileNode節點中建立的,但是是在PagerLoader中進行管理,在DatabasePager執行緒中由PagerLoader進行轉手(最終交由LoadTileData從讀取真正的瓦片資料)。
待繼續分析列表:
9、earth檔案中都有哪些options((九)中問題)
10、如何根據earth檔案options建立不同的地理資訊引擎節點((九)中問題)
11、rex地理資訊引擎的四樑八柱((九)中問題)
12、osgEarth::TerrainEngineNode中setMap方法作用((十二)中問題)
13、RexTerrainEngineNode中_mapFrame的作用((十二)中問題)
14、地形變形(Terrain morphing)((十二)中問題)
15、地球瓦片過期門限的含義((十二)中問題)
16、高解析度優先的含義((十二)中問題)
17、OSGEARTH_DEBUG_NORMALS環境變數的作用((十二)中問題)
18、活躍瓦片暫存器的作用((十二)中問題)
19、資源釋放器子節點的作用((十二)中問題)
20、共享幾何圖形池子節點的作用((十二)中問題)
21、分頁瓦片載入器子節點的作用((十二)中問題)
22、分頁瓦片解除安裝器子節點的作用((十二)中問題)
23、柵格化器子節點的作用((十二)中問題)
24、地形子節點的作用((十二)中問題)
25、繫結渲染器的作用((十二)中問題)
26、地圖回撥函式的作用((十二)中問題)
27、如何將地圖圖層新增到rex引擎中((十二)中問題)
28、選擇資訊的作用((十二)中問題)
29、瓦片包圍盒修改回撥函式的作用((十二)中問題)
30、重新整理rex引擎((十二)中問題)
31、重新整理邊界作用((十二)中問題)
32、osgEarth::Metrics類的意義((十四)中問題)
33、請求合併佇列_mergeQueue((十四)中問題)
34、分頁瓦片載入器在更新遍歷時對請求處理過程((十四)中問題)
35、分頁瓦片載入器在更新遍歷時對已處理請求裁剪過程((十四)中問題)
36、已處理的請求佇列_requests((十四)中問題)
37、DatabasePager中的_fileRequestQueue和_httpRequestQueue((十六)中問題)
38、瓦片請求的生成到處理過程詳解((十六)中問題)
39、瓦片節點TileNode的建立過程((十七)中問題)
40、request請求載入瓦片優先順序的含義((十七)中問題)
41、request的_internalHandle的作用((十七)中問題)
42、DatabaseRequest中_objectCache含義((十七)中問題)
42、osgEarth的多執行緒分析((十七)中問題)
43、osgEarth的快取及其結構((十七)中問題)
44、DatabaseThread從快取載入資料過程((十七)中問題)
45、DatabaseThread從檔案載入資料過程((十七)中問題)
46、決定建立TileNode的時機條件((十七)中問題)
47、TerrainEngineNode的createTileModel過程詳解((十七)中問題)
48、DatabaseThread中CompileSet的含義((十七)中問題)
48、PagerLoader的traverse過程詳解((十七)中問題)
49、DatabaseThread的run過程詳解((十七)中問題)
50、LoadTileData的invoke過程詳解((十七)中問題)
51、TileNode的cull過程詳解((十七)中問題)