Cocos2dx开发之运行与渲染流程分析
学习Cocos2dx,我们都知道程序是由 AppDelegate 的方法 applicationDidFinishLaunching 开始,在其中做些必要的初始化,并创建运行第一个 CCScene 即可。但实际我们并不知道程序运行时,何时调用 AppDelegate 的构造函数,析构函数和程序入口函数,这是问题一。另外在实际执行的过程中,程序只调用其构造函数和入口函数,而直到程序结束运行,都没有调用其析构函数,那么程序又是在哪里结束的呢?这是问题二。
首先,解决问题1,在windows下,可以在applicationDidFinishLaunching 方法内加断点跟踪,堆栈图如下:
  
一个程序一般是由main函数开始,Cocos2dx也不例外,在proj.win32/main.cpp路径下,存在main.cpp文件:
USING_NS_CC; int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine); // create the application instance
AppDelegate app;
return Application::getInstance()->run();
}
CCApplication-win32.cpp:
int Application::run()
{
... ... ...
if (!applicationDidFinishLaunching())
{
return ;
} auto director = Director::getInstance();
auto glview = director->getOpenGLView(); // Retain glview to avoid glview being released in the while loop
glview->retain(); while(!glview->windowShouldClose())
{
QueryPerformanceCounter(&nNow);
if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
//开启Cocos的主循环
director->mainLoop();
glview->pollEvents();
}
else
{
Sleep();
}
}
}
上面能观察到,在调用run方法之前先使用了 AppDelegate app,这样做的原因是 AppDelegate 是 CCApplication 的子类,在创建子类对象的时候,调用其构造函数的同时,父类构造函数也会执行,然后就将 AppDelegate 的对象赋给了 CCApplication 的静态变量。在 AppDelegate 中实现了 applicationDidFinishLaunching 方法,所以在 CCApplication 中 run 方法的开始处调用的就是 AppDelegate 之中的实现。
同理,在android平台下游戏是从从一个Activity开始的,启动Activity是在文件AppActivity.java中定义的,相关代码如下:
public class __PROJECT_PACKAGE_LAST_NAME_UF__ extends Cocos2dxActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    static {
        System.loadLibrary("game");
    }
}
在游戏启动时,Activity首先会执行静态代码块,加载game.so库,这动态链接库是在用NDK编译的时候生成的;然后就是执行onCreate方法,这里调用了父类Cocos2dxActivity的onCreate方法。 Cocos2dxActivity在
- $(sourcedir)\cocos2dx\platform\android\java\src\org\cocos2dx\lib\Cocos2dxActivity.java
 
其OnCreate方法代码如下:
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CocosPlayClient.init(this, false); onLoadNativeLibraries(); sContext = this;
this.mHandler = new Cocos2dxHandler(this);//处理安卓的弹窗等 Cocos2dxHelper.init(this); this.mGLContextAttrs = getGLContextAttrs();//获取OpenGL ES的相关属性
this.init(); if (mVideoHelper == null) {
mVideoHelper = new Cocos2dxVideoHelper(this, mFrameLayout);
} if(mWebViewHelper == null){
mWebViewHelper = new Cocos2dxWebViewHelper(mFrameLayout);
}
}
一个native的函数,即在java在调用C++代码实现的函数,即需要采用JNI技术(可以看成是Java与C++交互的一个协议~)。方法nativeInit对应的C++实现是在(sourcedir)\samples\\proj.android\jni\main.cpp中:
void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h)
{
auto director = cocos2d::Director::getInstance();
auto glview = director->getOpenGLView();
if (!glview)
{
glview = cocos2d::GLViewImpl::create("Android app");
glview->setFrameSize(w, h);
director->setOpenGLView(glview);
cocos2d::Application::getInstance()->run();
} ,,, ,,,, ,,,
}
由 Android 启动一个应用,通过各处调用,最终执行到了 Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit 函数,充当了main函数的功能,开启游戏的主循环。 到此问题一得以解决。
有个地方要注意的,就是上面run方法里并没有循环退出条件,所以 run 方法永远不会返回。那么是怎么结束的呢?这便抛出了问题2,下面分析一下:
void Director::purgeDirector()
{
... .... ...
// OpenGL view
if (_openGLView)
{
_openGLView->end();
_openGLView = nullptr;
}// delete Director
release();
} void CCEGLView::end()
{
glfwTerminate();
delete this;
exit();
}
游戏的运行以场景为基础,每时每刻都有一个场景正在运行,其内部有一个场景栈,遵循后进后出的原则,当我们显示的调用 end() 方法,或者弹出当前场景之时,其自动判断,如果没有场景存在,也会触发 end() 方法,以说明场景运行的结束,而游戏如果没有场景,就像演出没有了舞台,程序进入最后收尾的工作。
程序运行时期,由 mainLoop 方法维持运行着游戏之内的各个逻辑,当在弹出最后一个场景,或者直接调用 CCDirector::end(); 方法后,触发游戏的清理工作,执行 purgeDirector 方法,从而结束了 CCEGLView(不同平台不同封装,PC使用OpenGl封装,移动终端封装的为 OpenGl ES) 的运行,调用其 end() 方法,从而直接执行 exit(0); 退出程序进程,从而结束了整个程序的运行。
参考资料链接:
http://blog.csdn.net/maximuszhou/article/details/39448971
https://www.cnblogs.com/Monte/p/6735061.html
http://www.cocoachina.com/cocos/20130607/6356.html
Cocos2dx引擎的架构,我们可以总结成下面这个简单划分的模块系统来深入理解学习:
  
游戏主循环
在上面流程分析时候能看到所有的事件和渲染的内容都是实现在mainLoop的游戏主循环中,每帧做的内容如下:
  
UI树遍历
直接通过Cocos源码理解遍历过程,Node::Visit()方法如下:
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
// quick return if not visible. children won't be drawn.
if (!_visible)
{
return;
} uint32_t flags = processParentFlags(parentTransform, parentFlags); // IMPORTANT:
// To ease the migration to v3.0, we still support the Mat4 stack,
// but it is deprecated and your code should not rely on it
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); //(mask 2) bool visibleByCamera = isVisitableByVisitingCamera(); int i = ; if(!_children.empty())
{
sortAllChildren();
// draw children zOrder < 0 位于父节点之后
for( ; i < _children.size(); i++ )
{
auto node = _children.at(i); if (node && node->_localZOrder < )
node->visit(renderer, _modelViewTransform, flags); //(mask 1)
else
break;
}
// self draw
if (visibleByCamera)
this->draw(renderer, _modelViewTransform, flags);
for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
(*it)->visit(renderer, _modelViewTransform, flags);
}
else if (visibleByCamera)
{
this->draw(renderer, _modelViewTransform, flags);
}
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}
模型视图变换矩阵
Node维护有一模型视图变换矩阵,其由父亲的模型视图变换矩阵右乘当前节点在本地坐标系中的变换矩阵得到(如上 mask 2)。在遍历时,根节点的变换矩阵为单位矩阵,一次向下级传递自身的模型视图变换矩阵来计算子元素的模型视图变换矩阵(如上 mask 1),最后这个变换矩阵连同元素相关信息被传入OpenGL ES渲染管线。 通过传递并右乘的方式,有利于确保父节点下面的子节点都跟父亲做相同的模型视图变换,如根节点缩放并位移,其儿子也进行相同的矩阵变换。
新绘制系统
在Cocos2dx 2.x旧引擎版本里,每个UI元素的绘制逻辑(即渲染的GL命令)都分布在对应的内部draw函数中,紧密跟随UI树的遍历,换句话说就是遍历某个父节点得到的每个儿子马上执行绘制逻辑。这样的设计明显会带来两个问题,一是多个层级之间(即不同父节点下的子节点)无法调整绘制的顺序;二是不容易扩展对绘制性能的优化(如自动批处理)。为解决这些问题,3.x版本改进有以下的特点:
1)将绘制逻辑从主循环的UI树遍历中分离。
  
2)运用应用程序级别视口裁剪。若一个UI元素在场景中的坐标位于视窗区域以外,它不会将任何绘制命令发送到绘制栈。这将减少绘制栈上绘制命令的数量,也将减少绘制命令的排序时间,还减少对GPU的浪费(不同于OpenGL ES层在图元装配阶段将位于视口之外的图元丢弃或者裁剪,因为这阶段的作用说明已经发送了绘制指令,处于渲染管线工序中的某一步了,比较耗性能)。
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
#if CC_USE_CULLING
// Don't do calculate the culling if the transform was not updated
_insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; if(_insideBounds)
#endif
{
_quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, , transform, flags);
renderer->addCommand(&_quadCommand);
}
}
3)采用自动批绘制技术。不需要像2.x以前要把每个元素添加到一个spriteBatchNode上,在3.x引擎下当不同类型的UI元素的对应相关的绘制指令(即QuadCommond)在执行顺序上相邻,并且使用相同的纹理,着色器等绘制属性时,这些QuadCommond会自动组合到一起,形成一次绘制,即只会调用一次的OpenGL绘制指令。
  
以一Sprite实际渲染分析下这个渲染流程:
_quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, , transform, flags);
renderer->addCommand(&_quadCommand);
绘制流程可以分为3个阶段:生成绘制指令->对绘制指令进行排序->执行绘制指令。
生成绘制指令,指向renderer发送一RendererCommond(如QuadCommond)绘制指令,该指令不执行任何GL绘制命令,renderer会将RenderCommond放到绘制栈中。等UI元素全部遍历完毕就开始执行栈中的所有RendererCommond。这样抽离出来的好处是方便统一处理全部的绘制命令,一方面可以针对绘制做一些优化,如相邻且使用相同纹理的QuadCommond执行自动批;另一方面可以灵活调整不同UI层级之间元素的绘制顺序。
绘制排序,指绘制命令不一定是UI元素被遍历的顺序,3.x引擎可以使用globalZOrder变量直接设置元素的绘制顺序。绘制先后首先由globalZOrder决定,然后才是遍历顺序,如下图:
  
Cocos2dx开发之运行与渲染流程分析的更多相关文章
- Android系统开发--灯光系统之电池灯的流程分析
		
Android系统开发--Android灯光系统之电池灯的流程分析 前期系统准备 运行初始化,创建系统服务 创建电池服务,获得电池灯;创建监听者监听上报电池事件: mSystemServiceMana ...
 - Ogre内部渲染流程分析系列
		
come from:http://blog.csdn.net/weiqubo/article/details/6956005 要理解OGRE引擎,就要理解其中占很重要位置的 Renderable接口, ...
 - 从React渲染流程分析Diff算法
		
1.什么是虚拟DOM 在React中,render执行的结果得到的并不是真正的DOM节点,结果仅仅是轻量级的JavaScript对象,我们称之为virtual DOM. 简单的说,其实所谓的virtu ...
 - cocos2d-x游戏引擎核心(3.x)----启动渲染流程
		
(1) 首先,这里以win32平台下为例子.win32下游戏的启动都是从win32目录下main文件开始的,即是游戏的入口函数,如下: #include "main.h" #inc ...
 - [转贴]Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程
		
看了opengles有一段时间了,算是了解了一下下.然后,就在基本要决定还是回归cocos2dx 3.2的,看了这篇好文章,欣喜转之~ 推荐看原帖: Cocos2d-x3.2与OpenGL渲染总结(一 ...
 - Cocos2d-x3.3RC0的Android编译Activity启动流程分析
		
本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2 ...
 - cocos2d-x渲染流程
		
Cocos2Dx之渲染流程 发表于8个月前(2014-08-08 22:46) 阅读(3762) | 评论(2) 17人收藏此文章, 我要收藏 赞2 如何快速提高你的薪资?-实力拍“跳槽吧兄弟”梦 ...
 - Struts2运行流程分析
		
一.Struts2运行流程图: 二.运行流程分析: 1. 请求发送给StrutsPrepareAndExecuteFilter 2.StrutsPrepareAndExecuteFilter询问Act ...
 - 8、Struts2 运行流程分析
		
1.流程分析: 请求发送给 StrutsPrepareAndExecuteFilter StrutsPrepareAndExecuteFilter 询问 ActionMapper: 该请求是否是一个 ...
 
随机推荐
- mybatis关于ORM的使用以及设计(一)[ORM的初始化]
			
ORM WIKI中的解释.画重点 Object-relational mapping (ORM, O/RM, and O/R mapping tool) in computer science is ...
 - 二叉搜索树(BST)学习笔记
			
BST调了一天,最后遍历参数错了,没药救了-- 本文所有代码均使用数组+结构体,不使用指针! 前言--BFS是啥 BST 二叉搜索树是基于二叉树的一种树,一种特殊的二叉树. 二叉搜索树要么是一颗空树, ...
 - Git命令行入门
			
安装 下载与文档地址:https://git-scm.com/book/zh/v2 我使用的是linux系统,故使用命令行安装Git # apt-get install git 配置 # git co ...
 - Bootstrap treeview增加或者删除节点
			
参考(AddNode: http://blog.csdn.net/qq_25628235/article/details/51719917,deleteNode:http://blog.csdn.ne ...
 - peewee 通俗易懂版
			
Peewee作为Python ORM之一 优势:简单,小巧,易学习,使用直观 不足之处:需要手动创建数据库 基本使用流程 1⃣️根据需求定义好Model(表结构类) 2⃣️通过create_table ...
 - CentOS7怎么更换yum源
			
163yum源:1)备份当前yum源防止出现意外还可以还原回来cd /etc/yum.repos.d/cp /CentOS-Base.repo /CentOS-Base-repo.bak2)使用wge ...
 - LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal 由前序和中序遍历建立二叉树 C++
			
Given preorder and inorder traversal of a tree, construct the binary tree. Note:You may assume that ...
 - 如何利用Git生成pitch和打pitch
			
利用Git生成和应用patch 在程序员的日常开发与合作过程中,对于code的生成patch和打patch(应用patch)成为经常需要做的事情. 什么是patch?简单来讲,patch中存储的是你 ...
 - HTML5 前端将 dom 元素转化为 Word,EXCEL 或者图片 并实现下载
			
< 一 > word 1,依赖于 jquery.html.word.js 插件 => https://blog-static.cnblogs.com/files/lovling/ ...
 - Java mysql