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中的資源(這些資源被稱為“已聲明的”)的名稱來創建資源對象。需要強調的是,此時僅是創建資源對象而已,並未加載相應的資源數據。