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还引入了多种技术手段,下面将结合相关代码对此作一些简单的分析。
最令人印象深刻的大概要属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中的资源(这些资源被称为“已声明的”)的名称来创建资源对象。需要强调的是,此时仅是创建资源对象而已,并未加载相应的资源数据。