1. 程式人生 > >osgEarth的Rex引擎原理分析(十七)瓦片請求的生成到處理過程詳解

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過程詳解((十七)中問題)