Ogre源码分析(一)Root类,Facade模式

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中定义,它的头文件里还包含了这四个头文件:

  1. #include "OgrePrerequisites.h"
  2. #include "OgreSingleton.h"
  3. #include "OgreString.h"
  4. #include "OgreSceneManagerEnumerator.h"
  5. #include "OgreResourceGroupManager.h"

其他暂且不提,先说"OgrePrerequisites.h",以Prerequisites类为后缀的头文件通常有这样的作用:

1:预声明所有的类;2:定义编译器相关的环境变量,也就是一些预编译宏。这里多说两句,一般开源项目都会有这个头文件,通常有些和平台相关的预定义宏都在这个头文件中指定,开源的项目大多都是跨平台的么……,我这个小虾米也算见识见识大牛们的Newbility了。

之后直接进入Root类的分析,作为门面类(Facade)和整个Ogre的入口,Root类都做了哪些工作。

为了研究Root类,我们还需要做这样一个工作,那就是先一个简单的代码例子,说明一下Ogre程序的“Main”函数是怎样的。

  1. #include "ExampleApplication.h"
  2. class TutorialApplication : public ExampleApplication
  3. {
  4. public:
  5. TutorialApplication() { }
  6. ~TutorialApplication() { }
  7. protected:
  8. void createScene(void) { }
  9. };
  10. #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
  11. #define WIN32_LEAN_AND_MEAN
  12. #include "windows.h"
  13. INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
  14. #else
  15. int main(int argc, char **argv)
  16. #endif
  17. {
  18. // Create application object
  19. TutorialApplication app;
  20. try {
  21. app.go();
  22. } catch( Exception& e ) {
  23. #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
  24. MessageBox( NULL, e.what(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
  25. #else
  26. fprintf(stderr, "An exception has occurred: %s/n", e.what());
  27. #endif
  28. }
  29. return 0;
  30. }

仔细一看,好像没有Root类的影子耶。只有一个TutorialApplication和app.Go;这个东东,貌似TutorialApplication是继承于ExampleApplication,那么我们就再看看ExampleApplication究竟是何方神圣吧。

先看看ExampleApplication有哪些成员变量:

  1. Root *mRoot;
  2. Camera* mCamera;
  3. SceneManager* mSceneMgr;
  4. ExampleFrameListener* mFrameListener;
  5. RenderWindow* mWindow;Root
  6. Ogre::String mResourcePath;

果然,第一个就是Root类,其他的暂且不说,直接步入正题。TutorialApplication类通过执行一个go()方法启动整个游戏引擎,那么go()方法都有哪些操作呢?

  1. virtual void go(void)
  2. {
  3. if (!setup())
  4. return;
  5. mRoot->startRendering();
  6. // clean up
  7. destroyScene();
  8. }

首先来看,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呢……。

  1. void Root::startRendering(void)
  2. {
  3. assert(mActiveRenderer != 0);
  4. mActiveRenderer->_initRenderTargets();
  5. // Clear event times
  6. learEventTimes();
  7. // Infinite loop, until broken out of by frame listeners
  8. // or break out by calling queueEndRendering()
  9. mQueuedEnd = false;
  10. while( !mQueuedEnd )
  11. {
  12. //Pump messages in all registered RenderWindow windows
  13. WindowEventUtilities::messagePump();
  14. if (!renderOneFrame())
  15. break;
  16. }
  17. }

这个方法分两部分,一部分是初始化,另一部分是不断的执行渲染的循环。(A)初始化都做了哪些工作呢?首先看mActiveRenderer这是一个Ogre::RenderSystem对象,这个东东是做什么用的?简要解说:他提供了一组OpenGL或D3D的抽象接口,他提供了两组接口,高级和低级,这个基类基本什么也没有实现,需要派生类实现其接口。他调用的_initRenderTarget的作用是初始化所有渲染对象到这个渲染系统,猜想应该封装了一些底层的D3D或者OpenGL代码了。这里先埋个伏笔!

ClearEventTimes(),这是个什么玩意?“事件时间”又是什么玩意?是这个

  1. <p>std::deque<unsigned long> mEventTimes[FETT_COUNT];</p><p>
  2. 定时器原来就是一个unsigned long型对象的双向队列的数组。这个做什么用的,也暂时不研究,因为这个是非主流的,哈哈。ClearEventTimes(),的操作无非就是用一个循环吧这个双向队列数组中的所有双向队列都清零了。这里会用到一些std的容器的知识,没学过的小朋友们需要补补了。(其实我也不熟悉。)std::deque是一个高效的双端队列,可以高效地进行插入和删除操作。 </p>

然后就到了“主旋律”了:渲染的循环.WindowsEventUtility::messagePump做了什么呢,果不其然,他封装了消息泵。

  1. #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
  2. // Windows Message Loop (NULL means check all HWNDs belonging to this context)
  3. MSG msg;
  4. while( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
  5. {
  6. TranslateMessage( &msg );
  7. DispatchMessage( &msg );
  8. }

如果是Windows操作系统,就执行这个操作……,至于Linux,嘿嘿,不会!!!丫的直接略过。RenderOneFrame则是用来渲染一个帧的方法,API中有言,如果不想使用自动刷新的渲染机制,如某些不需要实时刷新的图形应用,可以再用手动方法,在每次需要刷新一帧的时候调用RenderOneFrame。接下来我们就来分析一下RenderOneFrame的代码。

RenderOneFrame,作用,渲染一帧,更新全部的渲染目标,在更新前后发出“帧事件”。它的实现如下:

  1. if(!_fireFrameStarted()) return false;
  2. if (!_updateAllRenderTargets())   return false;
  3. return _fireFrameEnded();

等等,_fireFrameStarted,_fireFrameEnded是什么东东,好奇怪的命名规则!鬼知道大牛们是怎么想的,但是有一点,以下划线开头的是比较低级的API,不以下划线开头的是比较高级的API。这个RenderOneFrame实质上是用一个高级接口封装了3个底层操作。重点在第二行代码“_updateAllRenderTargets() ”,但是第一和第三个也得提一提。

首先是_fireFrameStarted,他有两个重载,_fireFrameStarted()和_fireFrameStarted(FrameEvent& evt),前者定义一个FrameEvent对象,然后将其初始化好,并调用后一个方法,后一个方法首先剔除全部被标记需要剔除的帧监听器。然后向其他所有正在使用的监听器发送一个帧事件消息,帧事件消息主要包含当前时间和上一个事件是时间差,和当前帧与上一个帧的时间差。这个以后再分析。然后是_fireFrameEnded(),她的工作和_fireFrameStarted()基本一致,只不过是多了这样的任务:释放这一帧使用的所有临时缓冲,向所有后台的资源队列发送一个后台载入事件。

终于轮到“_updateAllRenderTargets() ”了:

  1. // 更新全部渲染目标,但是不交换缓冲
  2. mActiveRenderer->_updateAllRenderTargets(false);
  3. bool ret = _fireFrameRenderingQueued();//给用户程序使用队列的GPU时间片的机会
  4. // 最后交换缓冲。
  5. mActiveRenderer->_swapAllRenderTargetBuffers(mActiveRenderer->getWaitForVerticalBlank()); return ret;

从这里开始,RenderSystem类(或其派生类)的实例开始接管了操作了。至于_fireFrameRenderingQueued()他和,_fireFrameStarted,_fireFrameEnded没啥区别,也是用触发侦听器事件的。今天主要目标是了解Root类的机制,因此先不再继续深入了。接下来的内容重点分类分析一下,Root类是以怎样的一个Facade的模式,支撑起整个Ogre引擎。

2009年9月17日添加:

Ogre维护了一个资源搜索地址列表,需要查找资源的时候,就在这些列表的目录里寻找,这通过addResourceLocation方法实现,这其实是封装了ResourceGroupManager类的方法,看吧,又是Facade模式:)。【删除是用removeResourceLocation】估计也是用什么容器实现滴……。

这个思想是值得借鉴滴。

转:Ogre源码分析之Root类、Facade模式的更多相关文章

  1. JUC源码分析-其它工具类(一)ThreadLocalRandom

    JUC源码分析-其它工具类(一)ThreadLocalRandom ThreadLocalRandom 是 JDK7 在 JUC 包下新增的随机数生成器,它解决了 Random 在多线程下多个线程竞争 ...

  2. Struts2 源码分析——Action代理类的工作

    章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...

  3. 源码分析——Action代理类的工作

     Action代理类的新建 通过<Struts2 源码分析——调结者(Dispatcher)之执行action>章节我们知道执行action请求,最后会落到Dispatcher类的serv ...

  4. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. DolphinScheduler源码分析之EntityTestUtils类

    1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license ...

  6. mybatis源码分析(3)——SqlSessionManager类

    从上图可能看出,在 mybatis中,SqlSession的实现类有两个,其中SqlSessionManager类不但实现了SqlSession接口,同时也实现了SqlSessionFactory接口 ...

  7. 【Java源码分析】LinkedList类

    LinkedList<E> 源码解读 继承AbstractSequentialList<E> 实现List<E>, Deque<E>, Cloneabl ...

  8. Spring5源码分析之启动类的相关接口和注解

    一些基础但是核心的知识总结: Spring Boot项目启动的时候需要加@Configuration. @ComponentScan @Configuration + @Bean 把第三方jar包注入 ...

  9. React Fiber源码分析 第二篇(同步模式)

    先附上两张流程图 1.scheduleRootUpdate  这个函数主要执行了两个操作  1个是创建更新createUpdate并放到更新队列enqueueUpdate, 1个是执行sheculeW ...

随机推荐

  1. pchar,pwidechar,pansichar作为返回参数时内存访问错误

    function Test:pachr: var   str: string; begin   str := 'Test Char';   result:=pchar(str); end; 上面的Te ...

  2. C++中一些类和数据结构的大小的总结

    针对class,虚函数等情况写了一些代码测试. #include <stdio.h> class A { }; class B { public: void f(); void g(); ...

  3. solr4.7配置(ik-analyzer)

    环境: windows server 2003 sp2 x86 tomcat8.0 solr-4.7.2 IK Analyzer 2012FF_hf1 ————————————华丽的分割线—————— ...

  4. 客户端连接SQL报"Cannot Generate SSPI Context"错误

    这种错误实在是让人头痛, 如果你遇到它还没有头痛的话, 请先看看微软给出的针对这个错误的这篇KB811889. 一般我遇到这种错误都是直接放弃, 重新运行sysprep之后再安装一遍所需要的软件. 然 ...

  5. DIV+CSS规范命名集合

    我们开发CSS+DIV网页(Xhtml)时候,比较困惑和纠结的事就是CSS命名,特别是新手不知道什么地方该如何命名,怎样命名才是好的方法. 命名规则说明: 1).所有的命名最好都小写 2).属性的值一 ...

  6. 手把手实现腾讯qq拖拽删去效果(二)

    这节,就一个任务如何把上节自定义的翻页动画控件整进下拉列表中去. 由于是自定义的下拉列表控件,我们需要自定义能够上啦下滑的listview,这势必会造成这个问题,上拉刷新要响应相应touch事件,拖拽 ...

  7. Android -- Camera2(Android5.0)

    Camera2 Camera2是Android5.0中的其中一个新的特性,新的API.与原来的camera API相比,不同之处在于: 原生支持RAW照片输出 突发拍摄模式 制约拍照速度的不再是软件而 ...

  8. 经典算法题每日演练——第十一题 Bitmap算法 (转)

    http://www.cnblogs.com/huangxincheng/archive/2012/12/06/2804756.html 在所有具有性能优化的数据结构中,我想大家使用最多的就是hash ...

  9. libsvm使用说明

    http://www.hankcs.com/ml/libsvm-usage.html libsvm使用说明 码农场 > 机器学习 2016-02-18 阅读(345) 评论(0)  目录   l ...

  10. Mac关闭摄像头

    为了防止黑客入侵,我们可以手动执行命令,关掉摄像头设备的访问权限,让黑客去看黑夜吧. Mac关闭禁用摄像头的方法,打开终端,拷贝如下代码,回车,输入管理员密码,再次拷贝,回车. sudo chmod ...