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還引入了多種技術手段,下面將結合相關代碼對此作一些簡單的分析。

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

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM