osgDB::readNodeFile等函数源码剖析

目录

1. 前言

2. 预备知识

2.1. osgDB::Options类

2.2. osgDB::Registry类

2.3. 文件读写插件

2.4 . osgDB::ReaderWriter类

2.5. osgDB::ReaderWriter::ReadResult类

3. readNodeFile函数分析

3.1. 无Options对象且无文件读取回调函数

3.2. 有Options对象且其文件读取回调类有效

3.2.1. 利用Options设置读缓存

3.2.2. 设置读回调类

3.3. 整个流程宏观分析

4. 后记


1. 前言

        osg的文件读写接口主要是由osgDB库负责实现。可读写的文件包括:模型、图像、视频、字体、文本文档等。osgDB库提供了丰富的读写接口来读写这些文件,如下为osgDB库部分接口(限于篇幅,没有全部罗列出来):

osg::ref_ptr< T >  readRefFile (const std::string &filename) 
  
OSGDB_EXPORT osg::ref_ptr< osg::Image >  readRefImageFile (const std::string &filename, const Options *options) 
  Read an osg::Image from file. More...
 
  
osg::ref_ptr< osg::Image >  readRefImageFile (const std::string &filename) 
  Read an osg::Image from file. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::HeightField >  readRefHeightFieldFile (const std::string &filename, const Options *options) 
  Read an osg::HeightField from file. More...
 
  
osg::ref_ptr< osg::HeightField >  readRefHeightFieldFile (const std::string &filename) 
  Read an osg::HeightField from file. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::Node >  readRefNodeFile (const std::string &filename, const Options *options) 
  Read an osg::Node from file. More...
 
  
osg::ref_ptr< osg::Node >  readRefNodeFile (const std::string &filename) 
  Read an osg::Node from file. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::Node >  readRefNodeFiles (std::vector< std::string > &fileList, const Options *options) 
  Read an osg::Node subgraph from files, creating a osg::Group to contain the nodes if more than one subgraph has been loaded. More...
 
  
osg::ref_ptr< osg::Node >  readRefNodeFiles (std::vector< std::string > &fileList) 
  Read an osg::Node subgraph from files, creating a osg::Group to contain the nodes if more than one subgraph has been loaded. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::Node >  readRefNodeFiles (osg::ArgumentParser &parser, const Options *options) 
  Read an osg::Node subgraph from files, creating a osg::Group to contain the nodes if more than one subgraph has been loaded. More...
 
  
osg::ref_ptr< osg::Node >  readRefNodeFiles (osg::ArgumentParser &parser) 
  Read an osg::Node subgraph from files, creating a osg::Group to contain the nodes if more than one subgraph has been loaded. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::Shader >  readRefShaderFile (const std::string &filename, const Options *options) 
  Read an osg::Shader from file. More...
 
  
osg::ref_ptr< osg::Shader >  readRefShaderFile (const std::string &filename) 
  Read an osg::Shader from file. More...
 
  
osg::ref_ptr< osg::Shader >  readRefShaderFile (osg::Shader::Type type, const std::string &filename, const Options *options) 
  Read an osg::Shader from file and set to specified shader type. More...
 
  
osg::ref_ptr< osg::Shader >  readRefShaderFile (osg::Shader::Type type, const std::string &filename) 
  Read an osg::Shader from file and set to specified shader type Return valid osg::Shader on success, return NULL on failure. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::Shader >  readRefShaderFileWithFallback (osg::Shader::Type type, const std::string &filename, const Options *options, const char *fallback) 
  Read an osg::Shader from file and set to specified shader type, if a shader isn't loaded fallback to specific shader source. More...
 
  
osg::ref_ptr< osg::Shader >  readRefShaderFileWithFallback (osg::Shader::Type type, const std::string &filename, const char *fallback) 
  Read an osg::Shader from file and set to specified shader type, if a shader isn't loaded fallback to specific shader source. More...
 
  
OSGDB_EXPORT osg::ref_ptr< osg::Script >  readRefScriptFile (const std::string &filename, const Options *options) 
  Read an osg::Script from file. More...
 
  
osg::ref_ptr< osg::Script >  readRefScriptFile (const std::string &filename) 
  Read an osg::Script from file. More...
 

OSGDB_EXPORT bool  writeObjectFile (const osg::Object &object, const std::string &filename, const Options *options) 
  Write an osg::Object to file. More...
 
  
bool  writeObjectFile (const osg::Object &object, const std::string &filename) 
  Write an osg::Object to file. More...
 
  
OSGDB_EXPORT bool  writeImageFile (const osg::Image &image, const std::string &filename, const Options *options) 
  Write an osg::Image to file. More...
 
  
bool  writeImageFile (const osg::Image &image, const std::string &filename) 
  Write an osg::Image to file. More...
 
  
OSGDB_EXPORT bool  writeHeightFieldFile (const osg::HeightField &hf, const std::string &filename, const Options *options) 
  Write an osg::HeightField to file. More...
 
  
bool  writeHeightFieldFile (const osg::HeightField &hf, const std::string &filename) 
  Write an osg::HeightField to file. More...
 
  
OSGDB_EXPORT bool  writeNodeFile (const osg::Node &node, const std::string &filename, const Options *options) 
  Write an osg::Node to file. More... 

                                代码段1

搞清楚这些读写函数内部的实现机制,对于熟悉osg并为自己以后编写读取特定文件的插件是至关重要的。

      说明:

  • 本文采用osg 3.6.2版本进行讲解,读者osg版本如果不是3.6.2版本,代码上可能有细微的差别。
  • 本博文以 osgDB::readNodeFile函数读取cow.osg文件为例子讲解,其它的函数思想相似。

2. 预备知识

        一般地,我们都是通过类似如下代码读取模型文件到视景器中进行显示的:

int main(int argc, char *argv[])
{
    osgViewer::Viewer viewer;
    auto pCowNode = osgDB::readNodeFile("cow.osg");
    if (nullptr == pCowNode)
    {
        OSG_WARN << "node is null!";
        return 1;
    }

    auto pRoot = new osg::Group;
    pRoot->addChild(pCowNode );
    viewer.setSceneData(pRoot);

    return viewer.run();
}

代码段2 

readNodeFile等函数在include\osgDB\ReadFile文件中,readNodeFile有两个重载函数,如下:

osg::Node*  readNodeFile(const std::string& filename);
osg::Node*  readNodeFile(const std::string& filename,const Options* options);

代码段3  

第1个函数函数内部其实是调用的第2个readNodeFile函数,只不过采用Registry类自带的Options类对象,如下:

inline osg::Node*  readNodeFile(const std::string& filename)
{
    return readNodeFile(filename,Registry::instance()->getOptions());
}

代码段4 

总结

  • 外层调用方可以通过调用第1个函数读取模型,此时不用传入Options类对象,Registry类内部会用自己的Options类对象传入。
  • 外层调用方可以通过调用第2个函数读取模型,此时需要自己构造一个Options类对象对象作为第2个参数传入。
  • 第1个函数内部其实是调用的第2个函数。

2.1. osgDB::Options类

       osgDB库提供了用于保存各种文件读写参数的Options类,其基本定义如下(因为接口太多,下图只列出了一部分,具体参见:include\osgDB\Options文件中Options类):

图1 

Options类也可以设置读文件回调函数、将读取的模型缓存起来等。

2.2. osgDB::Registry类

          Registry类是一个单类工厂,用于存储一些读写器,这些读写器在运行的时候会被链接到osgDB内部,用于读取一些非标准的文件格式。Registry类的单例功能通过instance函数实现,如下:

Registry* Registry::instance(bool erase)
{
    static ref_ptr<Registry> s_registry = new Registry;
    if (erase)
    {
        s_registry->destruct();
        s_registry = 0;
    }
    return s_registry.get(); // will return NULL on erase
}

代码段5  

在instance函数函数下面,有如下宏定义:

OSG_INIT_SINGLETON_PROXY(ProxyInitRegistry, Registry::instance())

代码段6  

转到该宏定义:

#define OSG_INIT_SINGLETON_PROXY(ProxyName, Func) static struct ProxyName{ ProxyName() { Func; } } s_##ProxyName;

代码段7  

注意##表示的含义,如果不知道该含义,请参见:C++/C宏定义中## 连接符与# 符的含义

该宏为:

#define OSG_INIT_SINGLETON_PROXY(ProxyInitRegistry, Registry::instance())

展开该宏后为:

static struct ProxyInitRegistry                                            
{                                                                          
    ProxyInitRegistry()                                                    
    {                                                                      
      Registry::instance();                                                
    }                                                                      
} s_ProxyInitRegistry;                                                     

代码段8  

       即该宏调用ProxyInitRegistry结构体的构造函数创建了一个全局的、静态的、名称为ProxyInitRegistry的结构体对象s_ProxyInitRegistry。我们知道全局对象在可执行程序一启动时(在main函数调用之前)或dll加载成功时就会被构造。而在ProxyInitRegistry结构体的构造函数中调用了Registry::instance(),这就保证程序启动的时,Registry类的单例就存在了。

        Registry类构造函数主要做了如下工作:

  • 查找环境变量OSG_BUILD_KDTREES,以决定是否创建KD树。关于什么是KD树,请自行百度。
  • 查找环境变量OSG_EXPIRY_DELAY,以决定删除缓存对象的时期(具体参见removeExpiredObjectsInCache函数)。
  • 查找环境变量OSG_FILE_CACHE,以创建文件缓存对象。
  • 创建对象缓存成员变量_objectCache。
  • 将osga、zip作为默认文件扩展名加入到名为_archiveExtList的vector的成员变量中。
  • 注册文件扩展名的别名。将文件扩展名和文件扩展名的别名分别作为key、value插入到_extAliasMap的map容器成员变量中。
  • 注册一些获取远程文件的服务协议,以便能能从远程网络端获取文件。

2.3. 文件读写插件

       osg对每种数据文件的读取或写入都是以插件形式进行的,插件以动态链接库的形式存在,它们被视为osg系统的可选插件。所谓插件就是在由软件系统公开一系列公共应用程序接口的前提下,由第三方开发人员编写实现的功能模块。这些模块独立于系统的核心模块之外,不存在过多的代码耦合,只要插件的开发者的代码编写规范且经过足够的测试,插件就可以一直稳定且灵活地嵌合在主系统中,如下为主系统和插件之间的关系图:

图2 

       插件采用职责链的设计模式, 职责链设计模式是一种对象行为模式,即将有可能处理同一个请求的所有对象排列成一条链,并沿着这条链传递请求,直到其中一个对象处理了这个请求为止。这种机制可以有效避免请求发送者和接收者之间的耦合关系。例如,下图所示的一个软件开发任务的请求依次交由企业服务部门、装货部门、送货部门和研发部门检查,并最终决定由研发部门负责这一个工作,从中可以看出,将各个处理部门排列成链并依次检查请求,向后传递请求,直到接受请求的过程,这就是职责链机制工作的基本流程。

图3 

关于职责链设计模式更详细的说明,请参考:责任链模式详解 

2.4 . osgDB::ReaderWriter类

      osg文件读写插件的公共接口类是osgDB::ReaderWriter类。该类接口太多,暂不列出,请自行查看include\osgDB\ReaderWriter文件。所有格式类型的文件读写类,即:无论是osg中已经提供的插件,还是用户自定义的文件格式的导入导出插件,都必须从osgDB::ReaderWriter类派生并根据业务需要重写该类的相应虚函数,否则就违背了插件和主系统的接口规约,导致插件不能嵌入到主系统,从而导致主系统不能读取或导出文件。

2.5. osgDB::ReaderWriter::ReadResult类

         这个类保存了读取结果。用户插件实现的过程中可以直接将解析得到的节点、图像、或者Object对象传递给该类,也可以获取工作状态的枚举量,如:

                enum ReadStatus
                {
                    NOT_IMPLEMENTED, //!< read*() method not implemented in concrete ReaderWriter.
                    FILE_NOT_HANDLED, //!< File is not appropriate for this file reader, due to some incompatibility, but *not* a read error.
                    FILE_NOT_FOUND, //!< File could not be found or could not be read.
                    ERROR_IN_READING_FILE, //!< File found, loaded, but an error was encountered during processing.
                    FILE_LOADED, //!< File successfully found, loaded, and converted into osg.
                    FILE_LOADED_FROM_CACHE, //!< File found in cache and returned.
                    FILE_REQUESTED, //!< Asynchronous file read has been requested, but returning immediately, keep polling plugin until file read has been completed.
                    INSUFFICIENT_MEMORY_TO_LOAD //!< File found but not loaded because estimated required memory surpasses available memory.
                };

代码段9 

3. readNodeFile函数分析

osgDB::registry类的readNode函数是读取文件的入口,其定义如下:

        ReaderWriter::ReadResult readNode(const std::string& fileName,const Options* options, bool buildKdTreeIfRequired=true)
        {
            ReaderWriter::ReadResult result;
            if (options && options->getReadFileCallback()) result = options->getReadFileCallback()->readNode(fileName,options);
            else if (_readFileCallback.valid()) result = _readFileCallback->readNode(fileName,options);
            else result = readNodeImplementation(fileName,options);

            if (buildKdTreeIfRequired) _buildKdTreeIfRequired(result, options);

            return result;
        }

代码段10 

在该函数中,分了3种情况读取模型文件,下面详细分析这三种情况。

3.1. 无Options对象且无文件读取回调函数

   即对应上述代码的

 else result = readNodeImplementation(fileName,options);

代码段11 

分支。这是由于调用方在调用readNodeFile函数时,没有向readNodeFile函数传递第2个类型为Options类的参数,像代码段2所示。readNodeImplementation函数会调用 Registry的readImplementation函数,该函数定义如下:

ReaderWriter::ReadResult Registry::readImplementation(const ReadFunctor& readFunctor,Options::CacheHintOptions cacheHint)
{
    std::string file(readFunctor._filename);

    bool useObjectCache = false;
    const Options* options = readFunctor._options;
    ObjectCache* optionsCache = options ? options->getObjectCache() : 0;

    //Note CACHE_ARCHIVES has a different object that it caches to so it will never be used here
    if ((optionsCache || _objectCache.valid()) && cacheHint!=Options::CACHE_ARCHIVES)
    {
        useObjectCache= options ? (options->getObjectCacheHint()&cacheHint)!=0: false;
    }

    if (useObjectCache)
    {
        // search for entry in the object cache.
        osg::ref_ptr<osg::Object> object = optionsCache ? optionsCache->getRefFromObjectCache(file, options) : 0;

        if (!object && _objectCache.valid()) object = _objectCache->getRefFromObjectCache(file, options);

        if (object.valid())
        {
            if (readFunctor.isValid(object.get())) return ReaderWriter::ReadResult(object.get(), ReaderWriter::ReadResult::FILE_LOADED_FROM_CACHE);
            else return ReaderWriter::ReadResult("Error file does not contain an osg::Object");
        }

        ReaderWriter::ReadResult rr = read(readFunctor);
        if (rr.validObject())
        {
            // search AGAIN for entry in the object cache.
            object = _objectCache->getRefFromObjectCache(file, options);
            if (object.valid())
            {
                if (readFunctor.isValid(object.get())) return ReaderWriter::ReadResult(object.get(), ReaderWriter::ReadResult::FILE_LOADED_FROM_CACHE);
                else
                {
                    return ReaderWriter::ReadResult("Error file does not contain an osg::Object");
                }
            }

            // update cache with new entry.
            if (optionsCache) optionsCache->addEntryToObjectCache(file, rr.getObject(), 0.0, options);
            else if (_objectCache.valid()) _objectCache->addEntryToObjectCache(file, rr.getObject(), 0.0, options);
        }
        else
        {
            OSG_INFO<<"No valid object found for "<<file<<std::endl;
        }

        return rr;

    }
    else
    {
        ReaderWriter::ReadResult rr = read(readFunctor);
        return rr;
    }
}

代码段12 

本函数说明如下:

  1. 先检查传入的Options类对象是否为空。如果不为空,则进行第2步。
  2. 获取Options类对象中类型为ObjectCache的optionsCache,用于后续模型节点对象的缓存。
  3. 如果optionsCache不为nullptr或者Registry自带的模型缓冲对象_objectCache有效且缓存类型不为CACHE_ARCHIVES且外层通过Options类对象设置了需要缓存模型节点对象,则将需要缓存标志useObjectCache设置为true。
  4. 根据文件名和Options类对象从_objectCache等map容器中找到以前缓存的osg::Object,如果osg::Object为空,证明没有找到,则从Registry自带的模型缓冲对象_objectCache中找到以前缓存的osg::Object,如果找到了,且不为空且该对象有效,则直接将该对象返还给外层;如果没找到,则调用Registry::read函数读取文件获取模型节点,如果读取成功且有效,则将读取到的模型节点加入到optionsCache缓存对象或_objectCache缓存对象(当optionsCache为空时),然后将节点返还给调用方。
  5. 如果Options类对象为空,则直接调用Registry::read函数读取文件获取模型节点,如果读取成功且有效,然后将节点返还给调用方。

整个流程如下:

图4 

因为无Options类对象,所以该函数前面和Options对象有关的那个if语句不会执行,只执行如下的else代码段:

 ReaderWriter::ReadResult rr = read(readFunctor);

代码段13

断点进入read函数,如下:

ReaderWriter::ReadResult Registry::read(const ReadFunctor& readFunctor)
{
    for(ArchiveExtensionList::iterator aitr=_archiveExtList.begin();
        aitr!=_archiveExtList.end();
        ++aitr)
    {
        std::string archiveExtension = "." + (*aitr);

        std::string::size_type positionArchive = readFunctor._filename.find(archiveExtension+'/');
        if (positionArchive==std::string::npos) positionArchive = readFunctor._filename.find(archiveExtension+'\\');
        if (positionArchive!=std::string::npos)
        {
            std::string::size_type endArchive = positionArchive + archiveExtension.length();
            std::string archiveName( readFunctor._filename.substr(0,endArchive));
            std::string fileName(readFunctor._filename.substr(endArchive+1,std::string::npos));
            OSG_INFO<<"Contains archive : "<<readFunctor._filename<<std::endl;
            OSG_INFO<<"         archive : "<<archiveName<<std::endl;
            OSG_INFO<<"         filename : "<<fileName<<std::endl;

            ReaderWriter::ReadResult result = openArchiveImplementation(archiveName,ReaderWriter::READ, 4096, readFunctor._options);

            if (!result.validArchive()) return result;

            osgDB::Archive* archive = result.getArchive();

            //if valid options were passed through the read functor clone them
            //otherwise make new options
            osg::ref_ptr<osgDB::ReaderWriter::Options> options = readFunctor._options ?
                readFunctor._options->cloneOptions() :
                new osgDB::ReaderWriter::Options;

            options->setDatabasePath(archiveName);

            osg::ref_ptr<ReadFunctor> rf(readFunctor.cloneType(fileName, options.get()));

            result = rf->doRead(*archive);

            if (rf->isValid(result))
            {
                OSG_INFO<<"Read object from archive"<<std::endl;
                return result;
            }
            OSG_INFO<<"Failed to read object from archive"<<std::endl;
        }
    }

    // record the errors reported by readerwriters.
    typedef std::vector<ReaderWriter::ReadResult> Results;
    Results results;

    // first attempt to load the file from existing ReaderWriter's
    AvailableReaderWriterIterator itr(_rwList, _pluginMutex);
    for(;itr.valid();++itr)
    {
        ReaderWriter::ReadResult rr = readFunctor.doRead(*itr);
        if (readFunctor.isValid(rr)) return rr;
        else results.push_back(rr);
    }

    // check loaded archives.
    AvailableArchiveIterator aaitr(_archiveCache, _archiveCacheMutex);
    for(;aaitr.valid();++aaitr)
    {
        ReaderWriter::ReadResult rr = readFunctor.doRead(*aaitr);
        if (readFunctor.isValid(rr)) return rr;
        else
        {
            // don't pass on FILE_NOT_FOUND results as we don't want to prevent non archive plugins that haven't been
            // loaded yet from getting a chance to test for the presence of the file.
            if (rr.status()!=ReaderWriter::ReadResult::FILE_NOT_FOUND) results.push_back(rr);
        }
    }

    // now look for a plug-in to load the file.
    std::string libraryName = createLibraryNameForFile(readFunctor._filename);
    if (loadLibrary(libraryName)!=NOT_LOADED)
    {
        for(;itr.valid();++itr)
        {
            ReaderWriter::ReadResult rr = readFunctor.doRead(*itr);
            if (readFunctor.isValid(rr)) return rr;
            else results.push_back(rr);
        }
    }

    //If the filename contains a server address and wasn't loaded by any of the plugins, try to find a plugin which supports the server
    //protocol and supports wildcards. If not successfully use curl as a last fallback
    if (containsServerAddress(readFunctor._filename))
    {
        ReaderWriter* rw = getReaderWriterForProtocolAndExtension(
            osgDB::getServerProtocol(readFunctor._filename),
            osgDB::getFileExtension(readFunctor._filename)
        );

        if (rw)
        {
            return readFunctor.doRead(*rw);
        }
        else
        {
            return  ReaderWriter::ReadResult("Could not find the .curl plugin to read from server.");
        }
    }

    if (results.empty())
    {
        return ReaderWriter::ReadResult("Could not find plugin to read objects from file \""+readFunctor._filename+"\".");
    }

    // sort the results so the most relevant (i.e. ERROR_IN_READING_FILE is more relevant than FILE_NOT_FOUND) results get placed at the end of the results list.
    std::sort(results.begin(), results.end());
    ReaderWriter::ReadResult result = results.back();

    return result;
}

 代码段14

首先进入到一个for循环,该循环对_archiveExtList遍历。_archiveExtList前面提到过,其里面放的是osga和zip这两个文件后缀名。先看看要加载的模型文件名是否以/结尾(Linux下),如果不是,再看看是否以\结尾(Windows下),这样做是检测输入文件名参数的时候,传入的文件名的最后一个字符是否是斜杠分割符,如:E:\osg\OpenSceneGraph-Data\cow.osg\,然后再调用openArchiveImplementation函数,在该函数中,先检查在_archiveCache中是否已经有以前加载过的对应的文件名的模型结果,如果有,则直接返回该结果;如果没,则调用代码段12读取该文件对应的模型节点。_archiveCache是个map类型,如下:

std::map<std::string, osg::ref_ptr<osgDB::Archive> > 

其中,键为文件名去掉目录后的名称,值为osgDB::Archive对象的智能引用指针。osgDB::Archive继承关系图如下:

图5 

可以看到它是一个osg::ReaderWriter类型。

注意:

osgDB::Archive和前面的Options或Registry类的缓存对象的区别:Options或Registry类的缓存仅仅是把以前读取过的模型对象即osg::Object保存到内存,后续再获取同名文件的模型对象时不用再读取直接将以前的osg::Object返还给调用方;而osgDB::Archive还对缓存的osg::Object做了某些操作,如:压缩等,会更节省内存等。

        如果在上段提到的for循环还没读成功,则执行代码段14的48行及以后的代码。48~72行代码功能为:首先遍历_rwList存放的读写器类对象,尝试用每个读写器对象读取传入的文件名,如果读取成功且读取结果(指文件对应的Node对象)有效,则返回结果到外层调用方;类似地,遍历_archiveCache,检查每个osgDB::Archive中是否已经缓存了指定文件名的结果,如果有且结果(指文件对应的Node对象)有效,则返回结果到外层调用方。注意:如果是第一次执行到这里,_rwList、_archiveCache容器都是空的(这两个容器的值是什么时候插入的呢,参见后文描述)。

      代码段的第75行调用createLibraryNameForFile函数,该函数首先将文件扩展名都转为小写,便于在_extAliasMap中查找该扩展名对应的别名,因为在Registty类的构造函数中,文件扩展名和该扩展名对应的别名都是以小写插入到_extAliasMap中的(参见前面对Registry类描述)。为何需要建立文件扩展名和扩展名别名的类型为map的_extAliasMap呢?这是因为用户可能需要强制将某种类型与指定插件相关联,如:读取JPG类型文件时,不是用默认的osgdb_jpg插件,而是需要用osgdb_gdal插件来读取,此时就可以调用如下代码:

osgDB::Registry::instance()->addFileExtensionAlias("jpg", "gdal");

之后就可以直接从注册器中得到ReaderWriterGDAL插件对象,并用它来读取jpg,如:

osgDB::ReaderWriter* pRW = osgDB::Registry::instance()->getReaderWriterForExtension("gdal");

if(nullptr != pRW )
{
      // 读写jpg
}

接下来,根据不同的操作系统及debug还是release,拼接出osgdb_osgd.dll或osgdb_osg.dll 文件的路径,并在第76行通过调用Registry类的loadLibrary将Plugins osg加载到系统来。在Plugins osg工程的ReaderWriterOSG.cpp、ReaderWriterOSG2.cpp分别定义了OSGReaderWriter和OSGReaderWriter2类。ReaderWriterOSG.cpp存在如下的宏:

REGISTER_OSGPLUGIN(osg, OSGReaderWriter)

ReaderWriterOSG2.cpp存在如下的宏:

REGISTER_OSGPLUGIN( osg2, ReaderWriterOSG2 )

 以第1个为例,该宏为:


#define REGISTER_OSGPLUGIN(osg, OSGReaderWriter)

 展开宏(以第1个为例,第2个同理),即为如下:

    extern "C" void osgdb_osg(void) {} 
    static osgDB::RegisterReaderWriterProxy<OSGReaderWriter> g_proxy_OSGReaderWriter;

可以看到,这里调用构造函数创建了一个全局的静态对象g_proxy_OSGReaderWriter。我们知道全局对象在可执行程序一启动时(在main函数调用之前)或dll加载成功时就会被构造,即加载osgdb_osgd.dll成功后就会创建该对象,即如下代码的构造函数会被调用:

/** Proxy class for automatic registration of reader/writers with the Registry.*/
template<class T>
class RegisterReaderWriterProxy
{
    public:
        RegisterReaderWriterProxy()
        {
            if (Registry::instance())
            {
                _rw = new T;
                Registry::instance()->addReaderWriter(_rw.get());
            }
        }

       ........... // 其它代码略

};

而在构造函数中,new出了一个T类型,也就是文件读取器类,本例为OSGReaderWriter类,然后通过Registry类的addReaderWriter方法将构造出来的OSGReaderWriter类对象加入到前文所说的类型为vector的读写器容器_rwList中。

 说明:

  1.  如果想要读取一个自定义格式的文件,则必须像OSGReaderWriter类一样从ReaderWriter类派生,并重写相应的虚函数。
  2. 假设第1步的实现文件为MyReaderWriter.cpp, 则在MyReaderWriter.cpp文件中利用REGISTER_OSGPLUGIN宏按照前文所述,将自定义读写器注册到系统内部,该宏的第1个参数为该类型文件的扩展名,第2个参数为自定义的读写器类的名称。
  3. 将1、2步用dll来封装实现,dll文件名格式为:osgdb_ext.dll或osgdb_extd.dll,其中ext为文件扩展名,d表示debug版,没d的表示release版,并将生成的dll放到osgPlugins-xx.yy.zz目录,其中xx.yy.zz为osg版本号。

         一旦插件dll加载成功,则代码段14中的第78~83行就会遍历加载成功的插件中含有的读写器,并用该读写器类对象读文件,如果读成功且node节点是有效的,则返还给外层调用方。88~114判断文件是否是远程网络文件,如:以http、ftp等开头的文件,如果是则找到对应的插件读取网络文件,如果所有的插件都没找到,则默认用curl库来加载远程网络文件,如果读成功且node节点是有效的,则返还给外层调用方。整个流程如下图:

图6 

        代码段14中的第80行对插件中包含的每个读写器尝试读取文件,经过跟踪后发现进入到了OSGReaderWriter类的readNode函数,如下: 

        virtual ReadResult readNode(std::istream& fin, const Options* options) const
        {
            loadWrappers();

            fin.imbue(std::locale::classic());

            Input fr;
            fr.attach(&fin);
            fr.setOptions(options);

            osg::NodeList nodeList;

            // load all nodes in file, placing them in a group.
            while(!fr.eof())
            {
                osg::ref_ptr<Node> node = fr.readNode();
                if (node.valid()) nodeList.push_back(node);
                else fr.advanceOverCurrentFieldOrBlock();
            }

            if  (nodeList.empty())
            {
                return ReadResult("No data loaded");
            }
            else if (nodeList.size()==1)
            {
                return nodeList.front();
            }
            else
            {
                Group* group = new Group;
                group->setName("import group");
                for(NodeList::iterator itr=nodeList.begin();
                    itr!=nodeList.end();
                    ++itr)
                {
                    group->addChild(*itr);
                }
                return group;
            }

        }

 代码段15

在该函数通过调用loadWrappers函数加载osgPlugins-3.6.2/osgdb_deprecated_osgd.dll,即Plugins osg deprecated osg插件被加入到系统,可以看到该插件中的很多cpp文件都有如下的宏定义(以Group.cpp为例讲解):

// register the read and write functions with the osgDB::Registry.
REGISTER_DOTOSGWRAPPER(Group)
(
    new osg::Group,
    "Group",
    "Object Node Group",
    &Group_readLocalData,
    &Group_writeLocalData
);

代码段16

这个宏的作用就是利用osgDB::Registry类向系统注册读写相关的功能函数。将代码段16进行宏展开为:

    extern "C" void dotosgwrapper_Group(void) {} 
    static osgDB::RegisterDotOsgWrapperProxy dotosgwrapper_proxy_Group(
    new osg::Group,
    "Group",
    "Object Node Group",
    &Group_readLocalData,
    &Group_writeLocalData
);

 代码段17

即调用构造函数定义了一个全局的类型为osgDB::RegisterDotOsgWrapperProxy的静态对象dotosgwrapper_proxy_Group。我们知道全局对象在可执行程序一启动时(在main函数调用之前)或dll加载成功后就会被构造。而osgDB::RegisterDotOsgWrapperProxy构造函数如下:

RegisterDotOsgWrapperProxy::RegisterDotOsgWrapperProxy(osg::Object* proto,
                            const std::string& name,
                            const std::string& associates,
                            DotOsgWrapper::ReadFunc readFunc,
                            DotOsgWrapper::WriteFunc writeFunc,
                            DotOsgWrapper::ReadWriteMode readWriteMode)
{
    if (Registry::instance())
    {
        _wrapper = new DotOsgWrapper(proto,name,associates,readFunc,writeFunc,readWriteMode);
        Registry::instance()->getDeprecatedDotOsgObjectWrapperManager()->addDotOsgWrapper(_wrapper.get());
    }
}

代码段18 

  • 第1个参数proto:表示要处理的osg对象(如:osg::Group)的指针。
  • 第2个参数name:表示要处理的osg对象的节点名称(如:Group)。
  • 第3个参数associates:表示第1个参数在继承链向上除了osg::Referenced之外的父节点、祖父节点的名称和参数1对象自身的节点名称,且名称之间用空格隔开,如:Object Node Group。
  • 第4个参数readFunc:表示用于反序列化(即从文件读取)属性到第1个参数上时调用的读取函数。
  • 第5个参数writeFunc:表示将参数1指向的对象序列化(即将对象属性写入到文件)到文件时调用的写入函数。
  • 在构造函数中会调用addDotOsgWrapper将osg的节点名称和名称对应的对象构成一个名称为_objectWrapperMap的map,便于后续流程对osg文件的每个节点遍历及属性绑定。如下为_objectWrapperMap的内容:

 图7

之后,程序进入到代码段15的第16行,即循环文件中的每个节点对象并开始反序列化(即从文件读取)属性到代码段18中的第1个参数指向的osg对象上。osg格式的文件其实就是文本文件,用记事本打开如下:

图8

反序列化是在如下代码实现的:

osg::Object* DeprecatedDotOsgWrapperManager::readObject(DotOsgWrapperMap& dowMap,Input& fr)
{
   ......................................// 其它代码略

           for(DotOsgWrapper::Associates::const_iterator aitr=assoc.begin();
                                                          aitr!=assoc.end();
                                                          ++aitr)
            {
                DotOsgWrapperMap::iterator mitr = _objectWrapperMap.find(*aitr);
                if (mitr==_objectWrapperMap.end())
                {
                   ......................................// 其它代码略

                if (mitr!=_objectWrapperMap.end())
                {
                    DotOsgWrapper::ReadFunc rf = mitr->second->getReadFunc();
                    if (rf && (*rf)(*obj,fr)) iteratorAdvanced = true;
                }
            }

            if (!iteratorAdvanced) fr.advanceOverCurrentFieldOrBlock();
        }
     
    ......................................// 其它代码略
        return obj;

    }

    return 0L;
}

代码段19

其思路为:

  1. 根据节点名从_objectWrapperMap中找到对应的节点对象,如:根据“Group”找到osg::Group,然后将UniqueID和osg::Group构成键值对存入到map
  2. assoc容器中存放的是字符串Object、 Node、 Group(根据前文所述,其分别为Group的父或祖父或自身名称)。在for循环中遍历assoc,取出每个字符串并从_objectWrapperMap找到对应的对象,并调用该对象的读函数进行属性的读取,并将读取出来的属性绑定到对象上,如:osg:Object调用osg:Object自己的读函数、osg::Node调用osg::Node的读函数、osg::Group调用osg::Group的读函数。
  3. 步骤1、2完成后,对象的属性就全部获取到了,之后就把该对象返还外层。

       代码段19中的16行获取的就是代码段18中第4个参数设置的读函数的指针 ,代码段19中的17行就是调用读函数读取对象属性。整个流程如下图所示:

图9 

        至此,readNodeFile读文件流程完成!

3.2. 有Options对象且其文件读取回调类有效

           这种情况对应代码段10 中的if语句,即:

   if (options && options->getReadFileCallback()) result = options->getReadFileCallback()->readNode(fileName,options);

有时要对读文件加以控制,如:

  • 像前文提到,如果同一个文件需要读多次,可以先读取一次,获取到osg::Object对象后,将其缓存起来,再次获取时,直接从缓存中将对象返回给调用方,提高效率。
  • 在读文件之前或之后,需要做些准备或清理工作。
  • .........................。

以上可以通过设置osgDB::Options类的属性控制。osgDB::Options类的属性和方法很多,具体参见include\osgDB\Options。这里只说明缓存和读回调。

3.2.1. 利用Options设置读缓存

         前文讲解了读缓存机制,下面代码:

int main(int argc, char *argv[])
{
    osgViewer::Viewer viewer;

    osg::ref_ptr<osgDB::Options>spOptions = new osgDB::Options();
    spOptions->setObjectCacheHint(osgDB::Options::CacheHintOptions::CACHE_ALL);
    osg::ref_ptr<osgDB::ObjectCache>spObjectCache = new osgDB::ObjectCache();
    spOptions->setObjectCache(spObjectCache);
    auto pCowNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)", spOptions);
    if (nullptr == pCowNode)
    {
        OSG_WARN << "node is null!";
        return 1;
    }

    auto pCowNode1 = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)", spOptions);
    if (nullptr == pCowNode1)
    {
        OSG_WARN << "node is null!";
        return 1;
    }

    auto pRoot = new osg::Group;
    pRoot->addChild(pCowNode);
    pRoot->addChild(pCowNode1);
    viewer.setSceneData(pRoot);
    return viewer.run();
}

代码段20 

上面代码构造ObjectCache对象并将其设置到新构造的Options类对象上,然后再将Options类对象作为第2个参数传给readNodeFile。当读取pCowNode1节点时,就不会从文件读取,而是从缓存中直接取出osg::Object对象,具体参见前面的分析。

3.2.2. 设置读回调类

      当在读文件之前或之后,需要做些准备或清理工作时,就可以设置一个读回调类对象,当读取文件时,会执行预先读回调类的readNode函数。读回调类对象可通过Registry类来设置,也可通过Options类设置,以Options类设置的回调为优先,即Options类没设置读回调类对象时,才会调用Registry类来设置的读回调类。如下:

#include<osgViewer/Viewer>
#include<osgDB/readFile>
 
class MyReadFileCallback : public osgDB::Registry::ReadFileCallback
{
public:
    virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options)
    {
        std::cout << "before readNode" << std::endl;
        // note when calling the Registry to do the read you have to call readNodeImplementation NOT readNode, as this will
        // cause on infinite recusive loop.
        osgDB::ReaderWriter::ReadResult result = osgDB::Registry::instance()->readNodeImplementation(fileName, options);
        std::cout << "after readNode" << std::endl;
        return result;
    }
};

int main(int argc, char *argv[])
{
    osgViewer::Viewer viewer;

    osg::ref_ptr<osgDB::Options>spOptions = new osgDB::Options();
    auto pMyCallBack = new MyReadFileCallback();
    spOptions->setReadFileCallback(pMyCallBack);
    // osgDB::Registry::instance()->setReadFileCallback(pMyCallBack);
    auto pCowNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)", spOptions);
    if (nullptr == pCowNode)
    {
        OSG_WARN << "node is null!";
        return 1;
    }

    auto pRoot = new osg::Group;
    pRoot->addChild(pCowNode);
    viewer.setSceneData(pRoot);
    return viewer.run();
}

代码段21

在代码段21中,构造一个读回调类对象和一个Options对象,将读回调类对象设置到了Options对象上,此时读取文件时,就会调用MyReadFileCallback类的readNode函数,在该函数中做一些准备、读文件、清理工作。也可以不构造Options对象对象,而将第25行注释取消,利用Registry类设置读回调,且向osgDB::readNodeFile函数只传一个文件名的函数即可。

3.3. 整个流程宏观分析

       如下为osgDB::readNodeFile的整个流程图:

图10

如下为整个流程的实现机制(以读取扩展名osg的文件为例):

图11

     osgDB::ReadNodeFile三大核心功能模块为:

  • osgDB::Registry提供接口用于外层向本类注册读写器,并接收转发外层的读写调用。
  • 读写文件的插件如:Plugins osg 插件负责向osgDB::Registry注册扩展名为osg的文件读写器,并将读写任务转发到读写属性插件。
  • 读写属性插件如:Plugins osg deprecated osg,则负责读写osg相关对象的属性,并将这些属性绑附到对象上。

以上三者相互配合,且降低耦合,便于代码维护、功能扩展。

4. 后记

      osgDB::Read开头的几个读函数原理和流程和osgDB::ReadNodeFile类似,在此不再详述。看懂了上面的分析,理解osgDB::writeNodeFile函数就很简单了,顾名思义其是readNodeFile的逆函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值