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过程详解((十七)中问题)

猜你喜欢

转载自blog.csdn.net/hankern/article/details/84203643