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決定,而後纔是遍歷順序,以下圖:

  

相關文章
相關標籤/搜索