Ogre源代码浅析——资源管理逻辑结构(一)

Ogre把资源分为“Font”、“GpuProgram”、“Material”、“Mesh”、“Skeleton”和“Texture”等类型,它们分别用Font、GpuProgram、Material、Mesh、Skeleton、Texture等同名的类对象来描述,这些类都直接从Resource基类派生。Ogre的Resource对象都由ResourceManager来管理。不同类型资源的管理,分别由不同的资源管理器来实现,比如以上各种类型资源都对应着各自的资源管理器,FontManager、GpuProgramManager、MaterialManager、MeshManager、SkeletonManager、TextureManager等,它们都以ResourceManager作为自已的基类。各种类型资源类对象的创建、Load/Unload、销毁等操作,都由相应的ResourceManager来完成。但Ogre的对资源的管理还不仅限于此。为了更方便资源的使用,提高资源的使用的效率,Ogre还引入了多种技术手段,下面将结合相关代码对此作一些简单的分析。

平平笔记:
Resource —————— ResourceManager
Font ———————— FontManager
GpuProgram ———— GpuProgramManager
Material —————— MaterialManager
Mesh ——————— MeshManager
Skeleton —————— SkeletonManager
Texture —————— TextureManager

最令人印象深刻的大概要属Ogre引入的Group概念。Ogre中有一个被称为ResourceGroupManager的类,其中内嵌了一个ResourceGroup的结构定义,很明显定义ResourceGroup只是为了ResourceGroupManager内部使用。在需要进行3D场景展示的一般应用中,经常会遇到需要进行场景切换的时候,比如游戏中的关卡切换时,虚拟现实中角色由一个地点转换到另一个地点时等等。而在渲染每个场景时所需的资源往往涉及了所有的资源类型,一旦场景发生切换,当前所使用的大量资源都需要被逐一卸载,而新的场景所需的各类资源要逐一被加载。在游戏编程时,可以在自已编写的关卡管理器中处理类似工作,这明显会产生额外的工作量,更麻烦的是这部分代码逻辑可能需要在每个应用中被重复编写,而如果借助Ogre提供的ResourceGroup就可以直接方便地实现类似功能了。

以下是ResourceGroup定义的主要部分:

struct ResourceGroup
{
    enum Status
    {
        UNINITIALSED = 0,
        INITIALISING = 1,
        INITIALISED = 2,
        LOADING = 3,
        LOADED = 4
    };

/// Group name
String name;
/// Group status
Status groupStatus;
/// List of possible locations to search
LocationList locationList;
/// Index of resource names to locations, built for speedy access (case sensitive archives)
ResourceLocationIndex resourceIndexCaseSensitive;
    /// Index of resource names to locations, built for speedy access (case insensitive archives)
    ResourceLocationIndex resourceIndexCaseInsensitive;
/// Pre-declared resources, ready to be created
ResourceDeclarationList resourceDeclarations;
/// Created resources which are ready to be loaded / unloaded
// Group by loading order of the type (defined by ResourceManager)
// (e.g. skeletons and materials before meshes)
typedef map<Real, LoadUnloadResourceList*>::type LoadResourceOrderMap;
LoadResourceOrderMap loadResourceOrderMap;
    /// Linked world geometry, as passed to setWorldGeometry
    String worldGeometry;
    /// Scene manager to use with linked world geometry
    SceneManager* worldGeometrySceneManager;
// in global pool flag - if true the resource will be loaded even a different   group was requested in the load method as a parameter.
bool inGlobalPool;

void addToIndex(const String& filename, Archive* arch);
void removeFromIndex(const String& filename, Archive* arch);
void removeFromIndex(Archive* arch);
};

其中的name变量,用来标识group对象的名称。在生成ResourceGroup对象后,对象指针会被保存在ResourceGroupManager的mResourceGroupMap容器中并以name为key。来看下它的定义:

/// Map from resource group names to groups
typedef map<String, ResourceGroup*>::type ResourceGroupMap;
ResourceGroupMap mResourceGroupMap;

再来看ResourceGroup中LocationList的定义:

struct ResourceLocation
{
    /// Pointer to the archive which is the destination
    Archive* archive;
    /// Whether this location was added recursively
    bool recursive;
 };
/// List of possible file locations
typedef list<ResourceLocation*>::type LocationList;

可以看到ResourceLocation对象与Archive对象相比,只多了一个变量recursive,它主要用来表示对相应的Archive是否要进行递归操作(当Archive表示的目录中含有子目录时,一般要进行递归操作)因此ResourceLocation对象更完整地描述了资源所在目录的情况。

再来看ResourceGroup中ResourceLocationIndex和LoadUnloadResourceList的定义:

/// Resource index entry, resourcename->location 
typedef map<String, Archive*>::type ResourceLocationIndex;

/// List of resources which can be loaded / unloaded
typedef list<ResourcePtr>::type LoadUnloadResourceList;

在Ogre中**对资源进行使用和加载前先要对其进行定位,也就是要把待使用的资源的资源名、资源类型以及资源所在的路径关联起来。由于Ogre是以资源组(ResourceGroup)为单位对资源进行使用的,所以这个定位工作一般通过ResourceGroupManager对象来完成。ResourceGroupManager提供了addResourceLocation()**方法来实现这一功能。

void ResourceGroupManager::addResourceLocation(const String& name, 
    const String& locType, const String& resGroup, bool recursive)
{
    ResourceGroup* grp = getResourceGroup(resGroup);
    if (!grp)
    {
        createResourceGroup(resGroup);
        grp = getResourceGroup(resGroup);
    }

    OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME) // lock group mutex

    // Get archive
    Archive* pArch = ArchiveManager::getSingleton().load( name, locType );
    // Add to location list
    ResourceLocation* loc = OGRE_NEW_T(ResourceLocation, MEMCATEGORY_RESOURCE);
    loc->archive = pArch;
    loc->recursive = recursive;
    grp->locationList.push_back(loc);
    // Index resources
    StringVectorPtr vec = pArch->find("*", recursive);
    for( StringVector::iterator it = vec->begin(); it != vec->end(); ++it )
    grp->addToIndex(*it, pArch);
    
    StringUtil::StrStreamType msg;
    msg << "Added resource location '" << name << "' of type '" << locType
        << "' to resource group '" << resGroup << "'";
    if (recursive)
        msg << " with recursive option";
         LogManager::getSingleton().logMessage(msg.str());
}

addResourceLocation()的第一个参数“name”对应的资源所在目录的路径字符串;第二个参数locType对应着此目录下资源的类型——普通文件还是压缩文件;第三个参数resGroup表器要操作的group名称。Ogre会先根据resGroup在mResourceGroupMap中搜索相应的group,如果没找到就创建一个保存到mResourceGroupMap中;然后根据头两个参数生成相应的Archive对象和ResourceLocation对象,并把此ResourceLocation对象指针保存到第三个参数指定的group的locationList成员容器中。

接下来,Ogre会遍历Archive所表示的路径中的所有资源文件,并以资源文件的文件名为key将相应的Archive对象指针保存到ResourceGroup中的resourceIndexCaseSensitive和resourceIndexCaseInsensitive中(前者对资源文件名大小写敏感,后者不敏感),这样就把资源文件名与资源所在路径关联到了一起,为之后的资源加载作好准备,这是第一步。这一步工作可以手动逐条传入目录字符串来分别进行,也可以象例程中那样,通过外部配制文件来自动批处理。

Ogre中资源在加载到内存后,资源数据是保存在相应的资源对象中的,因此Ogre首先要根据需要生成资源对象,这是紧接上面第一步完成后第二步要做的工作。第二步完成后,Ogre会在内存中生成当前需要的所有的资源类对象,但此时的资源类对象中的数据是空的,相应的外部资源数据还没有真正加载进内存。这一步工作又被称为资源的初始化(initialise)。Ogre可以通过ResouceGroupManager的initialiseResourceGroup(const String& name)方法来对指定的group进行初始化,也可以通过initialiseAllResourceGroups()一次性对所有保存在mResourceGroupMap中的group中的资源进行初始化。来看相关代码:

void ResourceGroupManager::initialiseResourceGroup(const String& name)
{
    OGRE_LOCK_AUTO_MUTEX
    LogManager::getSingleton().logMessage("Initialising resource group " + name);
    ResourceGroup* grp = getResourceGroup(name);
    if (!grp)
    {
        OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, 
            "Cannot find a group named " + name, 
            "ResourceGroupManager::initialiseResourceGroup");
    }
    OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME) // lock group mutex

    if (grp->groupStatus == ResourceGroup::UNINITIALSED)
    {
        // in the process of initialising
        grp->groupStatus = ResourceGroup::INITIALISING;
        // Set current group
        parseResourceGroupScripts(grp);
        mCurrentGroup = grp;
        LogManager::getSingleton().logMessage("Creating resources for group " + name);
        createDeclaredResources(grp);
        grp->groupStatus = ResourceGroup::INITIALISED;
        LogManager::getSingleton().logMessage("All done");
        // Reset current group
        mCurrentGroup = 0;
    }
}

可以看到,在初始化开始之前,Ogre先将group的状态设为INITIALISIN,在初始化完成后状态被设成了INITIALISED。具体的初始化工作分为两个部分,一个是加载并实例化脚本,由于有些资源如材质(Material)、Shader(GpuProgram)等是由脚本文件描述的,所以对它们来说需要进行脚本加载和处理工作,这部分工作主要由parseResourceGroupScripts(grp)来完成;另一个工作如前所述是根据需要创建资源对象,这部分工作主要由createDeclareResources(grp)函数来完成。来看一下其中的代码:

void ResourceGroupManager::createDeclaredResources(ResourceGroup* grp)
{
    for (ResourceDeclarationList::iterator i = grp->resourceDeclarations.begin();
        i != grp->resourceDeclarations.end(); ++i)
    {
        ResourceDeclaration& dcl = *i;
        // Retrieve the appropriate manager
        ResourceManager* mgr = _getResourceManager(dcl.resourceType);
        // Create the resource
        ResourcePtr res = mgr->create(dcl.resourceName, grp->name,
                dcl.loader != 0, dcl.loader, &dcl.parameters);
        // Add resource to load list
            ResourceGroup::LoadResourceOrderMap::iterator li = 
                grp->loadResourceOrderMap.find(mgr->getLoadingOrder());
        LoadUnloadResourceList* loadList;
        if (li == grp->loadResourceOrderMap.end())
        {
            loadList = OGRE_NEW_T(LoadUnloadResourceList, MEMCATEGORY_RESOURCE)();
            grp->loadResourceOrderMap[mgr->getLoadingOrder()] = loadList;
        }
        else
        {
            loadList = li->second;
        }
        loadList->push_back(res);
     }
}

Ogre在之前的资源定位中,把所有待加载的资源与它所在的目录关联起来了。但在实际应用中,同一目录下的资源在某一阶段不需要全部加载,常常只需要加载其中的一部分。因此在上面所说的初始化过程中,就不能根据目录或者说Archive对象来创建资源对象,而应该根据实际需要来指定。指定要被创建的资源对象的信息就被保存在ResourceGroup中的resourceDeclaration里。看一下它的相关类型定义:

struct ResourceDeclaration
{
     String resourceName;
     String resourceType;
     ManualResourceLoader* loader; 
     NameValuePairList parameters;
};
/// List of resource declarations
typedef list<ResourceDeclaration>::type ResourceDeclarationList;

可以清楚地看到ResourceDeclaration中主要保存的是要被创建的资源的名称。因此ResourceGroupManager::createDeclaredResources()函数会根据事先保存在各group的resourceDeclaration中的资源(这些资源被称为“已声明的”)的名称来创建资源对象。需要强调的是,此时仅是创建资源对象而已,并未加载相应的资源数据。

转载自:https://www.cnblogs.com/yzwalkman/archive/2012/12/23/2830117.html

猜你喜欢

转载自blog.csdn.net/jean7155/article/details/85835800
今日推荐