Ogre中的Root对象是一个Ogre应用程序的主入口点。因为它是整个Ogre引擎的外观(Façade)类。通过Root对象来开启和停止Ogre是最简单的一种方式;当你构造构造一个Root实例的时候你就启动了整个Ogre,当析构的时候(让它停止活动或者执行delete删除它)Ogre也就关闭了。
API手册中这样介绍到:Ogre::Root 类代表了客户应用程序的入口点。在这里,应用程序可以获得系统的重要的访问权,也就是获取渲染系统 ,管理配置文件,日志,和访问系统的其他类(果然是Facade啊!)作为其他可接触的类对象的中心。一个Root 的实例必须在其他任何Ogre方法被调用之前被创建。一旦一个Ogre实例被创建,这个对象实例可以在这个对象的整个生命周期通过Root::getSingleton (引用)或 Root::getSingletonPtr (作为指针)被访问(传说中的单件模式也被用到了,这个设计模式偶认为是比较常用滴,换句话说,Root类的对象实例是唯一的,不用试图去创建一大堆Root的实例了!)。
首先来回顾一下Facade模式:【摘自“四人帮”的设计模式】
定义:为一组子系统的接口提供一个统一的接口,Facade模式提供一个高层接口使得子系统更易于被使用。
适用环境1:想为一个复杂的子系统提供一个简单的接口。2:用户和各个子系统之间有过多的依赖关系。3:希望使子系统层次化。
作用:使子系统更易于使用;降低子系统和用户间的耦合度;并不妨碍用户直接访问子系统。
Root类在OgreRoot.h中定义,它的头文件里还包含了这四个头文件:
- #include "OgrePrerequisites.h"
- #include "OgreSingleton.h"
- #include "OgreString.h"
- #include "OgreSceneManagerEnumerator.h"
- #include "OgreResourceGroupManager.h"
其他暂且不提,先说"OgrePrerequisites.h",以Prerequisites类为后缀的头文件通常有这样的作用:
1:预声明所有的类;2:定义编译器相关的环境变量,也就是一些预编译宏。这里多说两句,一般开源项目都会有这个头文件,通常有些和平台相关的预定义宏都在这个头文件中指定,开源的项目大多都是跨平台的么……,我这个小虾米也算见识见识大牛们的Newbility了。
之后直接进入Root类的分析,作为门面类(Facade)和整个Ogre的入口,Root类都做了哪些工作。
为了研究Root类,我们还需要做这样一个工作,那就是先一个简单的代码例子,说明一下Ogre程序的“Main”函数是怎样的。
- #include "ExampleApplication.h"
- class TutorialApplication : public ExampleApplication
- {
- public:
- TutorialApplication() { }
- ~TutorialApplication() { }
- protected:
- void createScene(void) { }
- };
- #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- #define WIN32_LEAN_AND_MEAN
- #include "windows.h"
- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
- #else
- int main(int argc, char **argv)
- #endif
- {
- // Create application object
- TutorialApplication app;
- try {
- app.go();
- } catch( Exception& e ) {
- #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- MessageBox( NULL, e.what(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
- #else
- fprintf(stderr, "An exception has occurred: %s/n", e.what());
- #endif
- }
- return 0;
- }
仔细一看,好像没有Root类的影子耶。只有一个TutorialApplication和app.Go;这个东东,貌似TutorialApplication是继承于ExampleApplication,那么我们就再看看ExampleApplication究竟是何方神圣吧。
先看看ExampleApplication有哪些成员变量:
- Root *mRoot;
- Camera* mCamera;
- SceneManager* mSceneMgr;
- ExampleFrameListener* mFrameListener;
- RenderWindow* mWindow;Root
- Ogre::String mResourcePath;
果然,第一个就是Root类,其他的暂且不说,直接步入正题。TutorialApplication类通过执行一个go()方法启动整个游戏引擎,那么go()方法都有哪些操作呢?
- virtual void go(void)
- {
- if (!setup())
- return;
- mRoot->startRendering();
- // clean up
- destroyScene();
- }
首先来看,go方法是一个虚函数,这里首先复习一下面向对象技术,纯虚函数就是要“只继承接口”,虚函数的就是要“继承接口和一部分实现(默认实现),且其变换性是存在的。”,非虚拟函数的意思是“子类要继承该方法的接口和全部的实现,不变性凌驾于变换性之上,绝对不要重写继承而来的非虚拟函数”。
当前的TutorialApplication没有重写ExampleApplication的go方法,也就是使用了ExampleApplication的默认实现,这个实现中一共调用了3个方法,setup,mRoot->startRendering();和destroyScene();setup和destroyScene方法只一笔掠过:destroyScene方法没有函数实现,估计也就是销毁场景的操作。
而setup方法做了这些操作:(1)初始化Root对象,设置配置文件(2)配置资源(3)配置启动文件(4)选择场景管理器(5)创建摄影机(6)创建视景体(7)创建默认mipmap等级(8)创建资源监听器(9)读取资源(10)创建场景(11)创建帧监听器。
最重要的就是mRoot->startRendering();方法了,我们回到API手册中查找这个方法究竟是何方妖孽,啊不,是何方神圣。API中说道,这个方法的作用是开启/重启自动的渲染循环。他不会返回,直到渲染循环停止。在渲染期间,任何通过addFrameListener方法注册的监听器类对象,都将在每一帧渲染完成之后被回调。 这些类可以告诉Ogre何时停止渲染循环,从而使该方法返回。开源就是好,我们来看看这个方法的实现部分。首先震惊一下!1291行代码,想想我的研究生论文的实验又有多少个1291呢……。
- void Root::startRendering(void)
- {
- assert(mActiveRenderer != 0);
- mActiveRenderer->_initRenderTargets();
- // Clear event times
- learEventTimes();
- // Infinite loop, until broken out of by frame listeners
- // or break out by calling queueEndRendering()
- mQueuedEnd = false;
- while( !mQueuedEnd )
- {
- //Pump messages in all registered RenderWindow windows
- WindowEventUtilities::messagePump();
- if (!renderOneFrame())
- break;
- }
- }
这个方法分两部分,一部分是初始化,另一部分是不断的执行渲染的循环。(A)初始化都做了哪些工作呢?首先看mActiveRenderer这是一个Ogre::RenderSystem对象,这个东东是做什么用的?简要解说:他提供了一组OpenGL或D3D的抽象接口,他提供了两组接口,高级和低级,这个基类基本什么也没有实现,需要派生类实现其接口。他调用的_initRenderTarget的作用是初始化所有渲染对象到这个渲染系统,猜想应该封装了一些底层的D3D或者OpenGL代码了。这里先埋个伏笔!
ClearEventTimes(),这是个什么玩意?“事件时间”又是什么玩意?是这个
- <p>std::deque<unsigned long> mEventTimes[FETT_COUNT];</p><p>
- 定时器原来就是一个unsigned long型对象的双向队列的数组。这个做什么用的,也暂时不研究,因为这个是非主流的,哈哈。ClearEventTimes(),的操作无非就是用一个循环吧这个双向队列数组中的所有双向队列都清零了。这里会用到一些std的容器的知识,没学过的小朋友们需要补补了。(其实我也不熟悉。)std::deque是一个高效的双端队列,可以高效地进行插入和删除操作。 </p>
然后就到了“主旋律”了:渲染的循环.WindowsEventUtility::messagePump做了什么呢,果不其然,他封装了消息泵。
- #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- // Windows Message Loop (NULL means check all HWNDs belonging to this context)
- MSG msg;
- while( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
- {
- TranslateMessage( &msg );
- DispatchMessage( &msg );
- }
如果是Windows操作系统,就执行这个操作……,至于Linux,嘿嘿,不会!!!丫的直接略过。RenderOneFrame则是用来渲染一个帧的方法,API中有言,如果不想使用自动刷新的渲染机制,如某些不需要实时刷新的图形应用,可以再用手动方法,在每次需要刷新一帧的时候调用RenderOneFrame。接下来我们就来分析一下RenderOneFrame的代码。
RenderOneFrame,作用,渲染一帧,更新全部的渲染目标,在更新前后发出“帧事件”。它的实现如下:
- if(!_fireFrameStarted()) return false;
- if (!_updateAllRenderTargets()) return false;
- return _fireFrameEnded();
等等,_fireFrameStarted,_fireFrameEnded是什么东东,好奇怪的命名规则!鬼知道大牛们是怎么想的,但是有一点,以下划线开头的是比较低级的API,不以下划线开头的是比较高级的API。这个RenderOneFrame实质上是用一个高级接口封装了3个底层操作。重点在第二行代码“_updateAllRenderTargets() ”,但是第一和第三个也得提一提。
首先是_fireFrameStarted,他有两个重载,_fireFrameStarted()和_fireFrameStarted(FrameEvent& evt),前者定义一个FrameEvent对象,然后将其初始化好,并调用后一个方法,后一个方法首先剔除全部被标记需要剔除的帧监听器。然后向其他所有正在使用的监听器发送一个帧事件消息,帧事件消息主要包含当前时间和上一个事件是时间差,和当前帧与上一个帧的时间差。这个以后再分析。然后是_fireFrameEnded(),她的工作和_fireFrameStarted()基本一致,只不过是多了这样的任务:释放这一帧使用的所有临时缓冲,向所有后台的资源队列发送一个后台载入事件。
终于轮到“_updateAllRenderTargets() ”了:
- // 更新全部渲染目标,但是不交换缓冲
- mActiveRenderer->_updateAllRenderTargets(false);
- bool ret = _fireFrameRenderingQueued();//给用户程序使用队列的GPU时间片的机会
- // 最后交换缓冲。
- mActiveRenderer->_swapAllRenderTargetBuffers(mActiveRenderer->getWaitForVerticalBlank()); return ret;
从这里开始,RenderSystem类(或其派生类)的实例开始接管了操作了。至于_fireFrameRenderingQueued()他和,_fireFrameStarted,_fireFrameEnded没啥区别,也是用触发侦听器事件的。今天主要目标是了解Root类的机制,因此先不再继续深入了。接下来的内容重点分类分析一下,Root类是以怎样的一个Facade的模式,支撑起整个Ogre引擎。
2009年9月17日添加:
Ogre维护了一个资源搜索地址列表,需要查找资源的时候,就在这些列表的目录里寻找,这通过addResourceLocation方法实现,这其实是封装了ResourceGroupManager类的方法,看吧,又是Facade模式:)。【删除是用removeResourceLocation】估计也是用什么容器实现滴……。
这个思想是值得借鉴滴。