Cocos2dx开发之运行与渲染流程分析

  学习Cocos2dx,咱们都知道程序是由 AppDelegate 的方法 applicationDidFinishLaunching 开始,在其中作些必要的初始化,并建立运行第一个 CCScene 便可。但实际咱们并不知道程序运行时,什么时候调用 AppDelegate 的构造函数,析构函数和程序入口函数,这是问题一。另外在实际执行的过程当中,程序只调用其构造函数和入口函数,而直到程序结束运行,都没有调用其析构函数,那么程序又是在哪里结束的呢?这是问题二。html

  首先,解决问题1,在windows下,能够在applicationDidFinishLaunching 方法内加断点跟踪,堆栈图以下:java

  

  一个程序通常是由main函数开始,Cocos2dx也不例外,在proj.win32/main.cpp路径下,存在main.cpp文件:node

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:android

int Application::run()
{
    ... ... ...
    if (!applicationDidFinishLaunching())
    {
        return 1;
    }

    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(1);
        }
    }
}

  上面能观察到,在调用run方法以前先使用了 AppDelegate app,这样作的缘由是 AppDelegate 是 CCApplication 的子类,在建立子类对象的时候,调用其构造函数的同时,父类构造函数也会执行,而后就将 AppDelegate 的对象赋给了 CCApplication 的静态变量。在 AppDelegate 中实现了 applicationDidFinishLaunching 方法,因此在 CCApplication 中 run 方法的开始处调用的就是 AppDelegate 之中的实现。windows

  同理,在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在app

  1. $(sourcedir)\cocos2dx\platform\android\java\src\org\cocos2dx\lib\Cocos2dxActivity.java 

  其OnCreate方法代码以下:ide

    @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函数的功能,开启游戏的主循环。 到此问题一得以解决。oop

  有个地方要注意的,就是上面run方法里并无循环退出条件,因此 run 方法永远不会返回。那么是怎么结束的呢?这便抛出了问题2,下面分析一下:

void Director::purgeDirector()
{
    ... .... ...
    // OpenGL view
    if (_openGLView)
    {
        _openGLView->end();
        _openGLView = nullptr;
    }// delete Director
    release();
}

void CCEGLView::end() 
{ 
    glfwTerminate(); 
    delete this; 
    exit(0); 
} 

  游戏的运行以场景为基础,每时每刻都有一个场景正在运行,其内部有一个场景栈,遵循后进后出的原则,当咱们显示的调用 end() 方法,或者弹出当前场景之时,其自动判断,若是没有场景存在,也会触发 end() 方法,以说明场景运行的结束,而游戏若是没有场景,就像演出没有了舞台,程序进入最后收尾的工做

  程序运行时期,由 mainLoop 方法维持运行着游戏以内的各个逻辑,当在弹出最后一个场景,或者直接调用 CCDirector::end(); 方法后,触发游戏的清理工做,执行 purgeDirector 方法,从而结束了 CCEGLView(不一样平台不一样封装,PC使用OpenGl封装,移动终端封装的为 OpenGl ES) 的运行,调用其 end() 方法,从而直接执行 exit(0); 退出程序进程,从而结束了整个程序的运行。

 

  参考资料连接:

  http://blog.csdn.net/maximuszhou/article/details/39448971

  http://www.javashuo.com/article/p-kzlclxra-dc.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 = 0;

    if(!_children.empty())
    {
        sortAllChildren();
        // draw children zOrder < 0  位于父节点以后
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);

            if (node && node->_localZOrder < 0)
                node->visit(renderer, _modelViewTransform, flags); //(mask 1else
                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, 1, transform, flags);
        renderer->addCommand(&_quadCommand);
    }
}

  3)采用自动批绘制技术。不须要像2.x之前要把每一个元素添加到一个spriteBatchNode上,在3.x引擎下当不一样类型的UI元素的对应相关的绘制指令(即QuadCommond)在执行顺序上相邻,而且使用相同的纹理,着色器等绘制属性时,这些QuadCommond会自动组合到一块儿,造成一次绘制,即只会调用一次的OpenGL绘制指令。

  

  以一Sprite实际渲染分析下这个渲染流程:

        _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform, flags);
        renderer->addCommand(&_quadCommand);

  绘制流程能够分为3个阶段:生成绘制指令->对绘制指令进行排序->执行绘制指令

  生成绘制指令,指向renderer发送一RendererCommond(如QuadCommond)绘制指令,该指令不执行任何GL绘制命令,renderer会将RenderCommond放到绘制栈中。等UI元素所有遍历完毕就开始执行栈中的全部RendererCommond。这样抽离出来的好处是方便统一处理所有的绘制命令,一方面能够针对绘制作一些优化,如相邻且使用相同纹理的QuadCommond执行自动批;另外一方面能够灵活调整不一样UI层级之间元素的绘制顺序

  绘制排序,指绘制命令不必定是UI元素被遍历的顺序,3.x引擎可使用globalZOrder变量直接设置元素的绘制顺序。绘制前后首先由globalZOrder决定,而后才是遍历顺序,以下图:

  

相关文章
相关标签/搜索