cocos2d-x游戲引擎核心(3.x)----啟動渲染流程


(1) 首先,這里以win32平台下為例子.win32下游戲的啟動都是從win32目錄下main文件開始的,即是游戲的入口函數,如下:

#include "main.h"
#include "AppDelegate.h"
#include "cocos2d.h"

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

(1-1)這里可以看出,在入口函數中,首先創建了一個AppDelegate對象,AppDelegate繼承 自CCApplication,在創建APPDelegate對象的時候就會隱式調用CCApplication構造函數,在這個構造函數里邊會將AppDelegate的this指針傳遞給全局共享對象sm_pSharedApplication,如下:

Application::Application()
//初始化win32應用程序對象 : _instance(nullptr) , _accelTable(nullptr) { _instance
= GetModuleHandle(nullptr);
  // 用於控制幀數的計數值 _animationInterval.QuadPart
= 0; CC_ASSERT(! sm_pSharedApplication);
// 全局共享對象 sm_pSharedApplication
= this; }

(1-2) 接下來調用Application::getInstance()->run();啟動游戲,如下:

int Application::run()
{
    PVRFrameEnableControlWindow(false);

    // Main message loop:
    LARGE_INTEGER nFreq;
    LARGE_INTEGER nLast;
    LARGE_INTEGER nNow;

    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nLast);

    // Initialize instance and cocos2d.
    // 執行AppDeletegate重載的applicationDidFinishLaunching函數
    if (!applicationDidFinishLaunching())
    {
        return 0;
    }

    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;
            // 主循環,每幀調用
            director->mainLoop();
            glview->pollEvents();
        }
        else
        {
            Sleep(0);
        }
    }

    // Director should still do a cleanup if the window was closed manually.
    if (glview->isOpenGLReady())
    {
        // 結束,執行清理工作
        director->end();
        director->mainLoop();
        director = nullptr;
    }
    glview->release();
    return true;
}

(1-2-1) 我們進入到AppDelegate::applicationDidFinishLaunching(),看它究竟做了什么,我們以/cocos2d-x-3.2/templates/cpp-template-default/Classes/AppDelegate.cpp為例:

bool AppDelegate::applicationDidFinishLaunching() {
    // initialize director
    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();
    if(!glview) {
// 創建glview對象, 這里采用默認的分辨率先創建出游戲窗口 glview
= GLView::create("My Game");
// 這里設置了和OpenGL相關的一些信息 director
->setOpenGLView(glview); } // turn on display FPS director->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this director->setAnimationInterval(1.0 / 60); // create a scene. it's an autorelease object
// 創建場景
auto scene = HelloWorld::createScene(); // run 運行場景 director->runWithScene(scene); return true; }

(1-2-1-1) 可以看到applicationDidFinishLaunching函數里面設置了glview對象之后,就開始運行場景,可以進入GLView::create中看其究竟是如何創建GLView對象,同樣,我們是win32下面看的, 所以找到cocos2d-x-3.2/cocos/platform/desktop/CCGLView.cpp文件:

GLView* GLView::create(const std::string& viewName)
{
    auto ret = new GLView;
    if(ret && ret->initWithRect(viewName, Rect(0, 0, 960, 640), 1)) {
        ret->autorelease();
        return ret;
    }

    return nullptr;
}

從代碼可以看到只是簡單的new一個GLView對象,我們進入/cocos2d-x-3.2/cocos/platform/desktop/CCGLView.h看一下它究竟是個什么東西:

/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2013-2014 Chukong Technologies Inc.

http://www.cocos2d-x.org

*/

#ifndef __CC_EGLVIEW_DESKTOP_H__
#define __CC_EGLVIEW_DESKTOP_H__

#include "base/CCRef.h"
#include "platform/CCCommon.h"
#include "platform/CCGLViewProtocol.h"
#include "glfw3.h"

NS_CC_BEGIN

class CC_DLL GLView : public GLViewProtocol, public Ref
{
public:
    static GLView* create(const std::string& viewName);
    static GLView* createWithRect(const std::string& viewName, Rect size, float frameZoomFactor = 1.0f);
    static GLView* createWithFullScreen(const std::string& viewName);
    static GLView* createWithFullScreen(const std::string& viewName, const GLFWvidmode &videoMode, GLFWmonitor *monitor);

    /*
     *frameZoomFactor for frame. This method is for debugging big resolution (e.g.new ipad) app on desktop.
     */

    //void resize(int width, int height);

    float getFrameZoomFactor();
    //void centerWindow();

    virtual void setViewPortInPoints(float x , float y , float w , float h);
    virtual void setScissorInPoints(float x , float y , float w , float h);


    bool windowShouldClose();
    void pollEvents();
    GLFWwindow* getWindow() const { return _mainWindow; }

    /* override functions */
    virtual bool isOpenGLReady() override;
// 刪除窗口,做窗口清理工作
virtual void end() override;
// 交換buffer
virtual void swapBuffers() override;
// 設置窗口大小
virtual void setFrameSize(float width, float height) override;
// 設置輸入法狀態
virtual void setIMEKeyboardState(bool bOpen) override; /* * Set zoom factor for frame. This method is for debugging big resolution (e.g.new ipad) app on desktop. */ void setFrameZoomFactor(float zoomFactor); /** Retina support is disabled by default * @note This method is only available on Mac. */ void enableRetina(bool enabled); /** Check whether retina display is enabled. */ bool isRetinaEnabled() const { return _isRetinaEnabled; }; /** Get retina factor */ int getRetinaFactor() const { return _retinaFactor; } protected: GLView(); virtual ~GLView(); bool initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor); bool initWithFullScreen(const std::string& viewName); bool initWithFullscreen(const std::string& viewname, const GLFWvidmode &videoMode, GLFWmonitor *monitor); bool initGlew(); void updateFrameSize(); // GLFW callbacks void onGLFWError(int errorID, const char* errorDesc); void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify); void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y); void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y); void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); void onGLFWCharCallback(GLFWwindow* window, unsigned int character); void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y); void onGLFWframebuffersize(GLFWwindow* window, int w, int h); void onGLFWWindowSizeFunCallback(GLFWwindow *window, int width, int height); bool _captured; bool _supportTouch; bool _isInRetinaMonitor; bool _isRetinaEnabled; int _retinaFactor; // Should be 1 or 2 float _frameZoomFactor; GLFWwindow* _mainWindow; GLFWmonitor* _monitor; float _mouseX; float _mouseY; friend class GLFWEventHandler; private: CC_DISALLOW_COPY_AND_ASSIGN(GLView); }; NS_CC_END // end of namespace cocos2d #endif // end of __CC_EGLVIEW_DESKTOP_H__

GLView繼承自GLViewProtocol,我們也進入看一下:

/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2013-2014 Chukong Technologies Inc.

http://www.cocos2d-x.org

*******************************************************/

#ifndef __CCGLVIEWPROTOCOL_H__
#define __CCGLVIEWPROTOCOL_H__

#include "base/ccTypes.h"
#include "base/CCEventTouch.h"

#include <vector>
// 5種屏幕適配策略
enum class ResolutionPolicy
{
    EXACT_FIT,
    NO_BORDER,
    SHOW_ALL,
    FIXED_HEIGHT,
    FIXED_WIDTH,

    UNKNOWN,
};

NS_CC_BEGIN

class CC_DLL GLViewProtocol
{
public:
    /**
     * @js ctor
     */
    GLViewProtocol();
    /**
     * @js NA
     * @lua NA
     */
    virtual ~GLViewProtocol();

    /** Force destroying EGL view, subclass must implement this method. */
    virtual void end() = 0;

    /** Get whether opengl render system is ready, subclass must implement this method. */
    virtual bool isOpenGLReady() = 0;

    /** Exchanges the front and back buffers, subclass must implement this method. */
    virtual void swapBuffers() = 0;

    /** Open or close IME keyboard , subclass must implement this method. */
    virtual void setIMEKeyboardState(bool open) = 0;

    /**
     * Polls input events. Subclass must implement methods if platform
     * does not provide event callbacks.
     */
    virtual void pollInputEvents();

    /**
     * Get the frame size of EGL view.
     * In general, it returns the screen size since the EGL view is a fullscreen view.
     */
    virtual const Size& getFrameSize() const;

    /**
     * Set the frame size of EGL view.
     */
    virtual void setFrameSize(float width, float height);

// 獲取可見區域的原點和大小 virtual Size getVisibleSize() const; virtual Vec2 getVisibleOrigin() const; virtual Rect getVisibleRect() const;

//
設置設計的size,當需要適配多種設備時,可以用這個函數定義邏輯坐標,cocos2dx會自動將邏輯坐標轉化成實際坐標,這樣一樣的代碼可以適配各種設備分辨率 virtual void setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy); /** Get design resolution size. * Default resolution size is the same as 'getFrameSize'. */ virtual const Size& getDesignResolutionSize() const; /** * Set opengl view port rectangle with points. */ virtual void setViewPortInPoints(float x , float y , float w , float h); /** * Set Scissor rectangle with points. */ virtual void setScissorInPoints(float x , float y , float w , float h); /** * Get whether GL_SCISSOR_TEST is enable */ virtual bool isScissorEnabled(); /** * Get the current scissor rectangle */ virtual Rect getScissorRect() const; virtual void setViewName(const std::string& viewname); const std::string& getViewName() const; /** Touch events are handled by default; if you want to customize your handlers, please override these functions: */
// 觸摸處理函數,可以重載
virtual void handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[]); virtual void handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[]); virtual void handleTouchesEnd(int num, intptr_t ids[], float xs[], float ys[]); virtual void handleTouchesCancel(int num, intptr_t ids[], float xs[], float ys[]); /** * Get the opengl view port rectangle. */ const Rect& getViewPortRect() const; /** * Get scale factor of the horizontal direction. */ float getScaleX() const; /** * Get scale factor of the vertical direction. */ float getScaleY() const; /** returns the current Resolution policy */ ResolutionPolicy getResolutionPolicy() const { return _resolutionPolicy; } protected: void updateDesignResolutionSize(); void handleTouchesOfEndOrCancel(EventTouch::EventCode eventCode, int num, intptr_t ids[], float xs[], float ys[]); // real screen size Size _screenSize; // resolution size, it is the size appropriate for the app resources. Size _designResolutionSize; // the view port size Rect _viewPortRect; // the view name std::string _viewName; float _scaleX; float _scaleY; ResolutionPolicy _resolutionPolicy; }; // end of platform group /// @} NS_CC_END #endif /* __CCGLVIEWPROTOCOL_H__ */

以看到CCEGLView和GLViewProtocol是顯示窗口,負責窗口級別的功能管理和實現, 包括:坐標和縮放管理, 畫圖工具,按鍵事件;

(1-2-1-2) 創建glview對象之后,導演類Director就把glview設置進游戲,其中包括很多配置信息, 如設置屏幕大小適配相關的函數getDesignResolutionSize, 如下:

void Director::setOpenGLView(GLView *openGLView)
{
    CCASSERT(openGLView, "opengl view should not be null");

    if (_openGLView != openGLView)
    {
        // Configuration. Gather GPU info
        Configuration *conf = Configuration::getInstance();
        conf->gatherGPUInfo();
        CCLOG("%s\n",conf->getInfo().c_str());

        if(_openGLView)
            _openGLView->release();
        _openGLView = openGLView;
        _openGLView->retain();

        // set size 設置屏幕大小適配相關的函數
        _winSizeInPoints = _openGLView->getDesignResolutionSize();

        createStatsLabel();

        if (_openGLView)
        {
            setGLDefaultValues();
        }
       // 完成初始化
        _renderer->initGLView();

        CHECK_GL_ERROR_DEBUG();

        if (_eventDispatcher)
        {
            _eventDispatcher->setEnabled(true);
        }
    }
}

(1-2-1-2-1) 我們進入initGLView看看它都做了什么初始化工作,找到/cocos2d-x-3.2/cocos/renderer/CCRenderer.cpp:

void Renderer::initGLView()
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
    _cacheTextureListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event){
        /** listen the event that renderer was recreated on Android/WP8 */
        this->setupBuffer();
    });
    
    Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_cacheTextureListener, -1);
#endif // 填充索引緩沖
    setupIndices();
    
    setupBuffer();
    
    _glViewAssigned = true;
}

(1-2-1-2-1-1) 進入setupIndices如下:

void Renderer::setupIndices()
{
    for( int i=0; i < VBO_SIZE; i++)
    {
    // 計算索引緩沖值 _indices[i
*6+0] = (GLushort) (i*4+0); _indices[i*6+1] = (GLushort) (i*4+1); _indices[i*6+2] = (GLushort) (i*4+2); _indices[i*6+3] = (GLushort) (i*4+3); _indices[i*6+4] = (GLushort) (i*4+2); _indices[i*6+5] = (GLushort) (i*4+1); } }

(1-2-1-2-1-2) 進入setupBuffer如下:

 

void Renderer::setupBuffer()
{
// 如果使用VAO
if(Configuration::getInstance()->supportsShareableVAO()) {
// 初始化VAO和VBO setupVBOAndVAO(); }
else {
// 初始化VBO setupVBO(); } }

 

(1-2-1-2-1-2-1) 進入setupVBOAndVAOsetupVBO, 開始調用OpenGL API進行頂點數據指定,具體意義參見基於Cocos2d-x學習OpenGL ES 2.0系列——編寫自己的shader(2):

void Renderer::setupVBOAndVAO()
{
    glGenVertexArrays(1, &_quadVAO);
    GL::bindVAO(_quadVAO);

    glGenBuffers(2, &_buffersVBO[0]);

    glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * VBO_SIZE, _quads, GL_DYNAMIC_DRAW);

    // vertices
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, vertices));

    // colors
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, colors));

    // tex coords
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORD);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, texCoords));

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * VBO_SIZE * 6, _indices, GL_STATIC_DRAW);

    // Must unbind the VAO before changing the element buffer.
    GL::bindVAO(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    CHECK_GL_ERROR_DEBUG();
}

void Renderer::setupVBO()
{
    glGenBuffers(2, &_buffersVBO[0]);

    mapBuffers();
}

 

(1-2-2) 在applicationDidFinishLaunching里面創建場景之后,就調用director->mainLoop();開始游戲主循環了.我們進入mainLoop看它做了什么, win32下我們找到cocos2d-x-3.2/cocos/base/CCDirector.cpp:

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
// 主循環結束,清除工作 purgeDirector(); }
else if (! _invalid) {
     // 渲染場景 drawScene();
// release the objects
// 釋放對象:內存池里之前通過autorelease加入的對象引用計數減 1.
PoolManager::getInstance()->getCurrentPool()->clear(); } }

mainLoop主要完成三個動作:

  1 判斷是否需要釋放 CCDirector,如果需要,則刪除 CCDirector 占用的資源。通常,游戲結束時才會執行這個步驟。  

  2 調用 drawScene()方法,繪制當前場景並進行其他必要的處理。

  3 彈出自動回收池,使得這一幀被放入自動回收池的對象全部釋放。

(1-2-2-1) 由此可見,mainLoop()把內存管理以外的操作都交給了 drawScene()方法,因此關鍵的步驟都在 drawScene()方法之中。下面是 drawScene()方法的實現:

// Draw the Scene
void Director::drawScene()
{
    // calculate "global" dt
// 計算全局幀間時間差 dt calculateDeltaTime(); // skip one flame when _deltaTime equal to zero. if(_deltaTime < FLT_EPSILON) { return; } if (_openGLView) { _openGLView->pollInputEvents(); } //tick before glClear: issue #533 if (! _paused) {
// 啟動定時器 _scheduler
->update(_deltaTime); _eventDispatcher->dispatchEvent(_eventAfterUpdate); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* to avoid flickr, nextScene MUST be here: after tick and before draw. XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ if (_nextScene) {
// 如果有,設置下一個場景 setNextScene(); }
// 保存原來的模型視圖(ModelView)矩陣 pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
// draw the scene if (_runningScene) {
     // 開始繪制場景 _runningScene
->visit(_renderer, Mat4::IDENTITY, false);
// 事件分發 _eventDispatcher
->dispatchEvent(_eventAfterVisit); } // draw the notifications node if (_notificationNode) {
// 處理通知節點 _notificationNode
->visit(_renderer, Mat4::IDENTITY, false); } if (_displayStats) { showStats(); } // 開始渲染場景 _renderer->render(); _eventDispatcher->dispatchEvent(_eventAfterDraw); popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); _totalFrames++; // swap buffers 交換緩沖區 if (_openGLView) { _openGLView->swapBuffers(); } if (_displayStats) { calculateMPF(); } }

可以發現drawScene主要用於處理 OpenGL 和一些細節,如計算 FPS、幀間時間差等,這里我們主要進行了以下 3 個操作。
  1 調用了定時調度器的 update 方法,引發定時器事件。
  2 如果場景需要被切換,則調用 setNextScene 方法,在顯示場景前切換場景。
  3 調用當前場景的 visit 方法,將當前場景加入渲染隊列,並通過render統一渲染。

(1-2-2-1-1) 我們進入到visit方法里面,看它怎樣把每一個節點添加到渲染隊列, 這里我們找到/cocos2d-x-3.2/cocos/2d/CCNode.cpp:

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    // quick return if not visible. children won't be drawn.
    if (!_visible)
    {
        return;
    }

// 設置_modelViewTransform矩陣 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* director = Director::getInstance(); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); 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); else break; } // self draw this->draw(renderer, _modelViewTransform, flags); for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) (*it)->visit(renderer, _modelViewTransform, flags); } else { this->draw(renderer, _modelViewTransform, flags); } director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // FIX ME: Why need to set _orderOfArrival to 0?? // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920 // reset for next frame // _orderOfArrival = 0; }

(1-2-2-1-1-1) 對節點的所有孩子排序,通過調用draw函數,首先繪制ZOrder<0的節點,在繪制自身,最后繪制ZOrder>0的節點. 我們進入draw看看它做些什么. 注意,visit和draw都是虛函數, 以sprite為例,我們進入到/cocos2d-x-3.2/cocos/2d/CCSprite.cpp:

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    // Don't do calculate the culling if the transform was not updated
    _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
    {
        _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
        renderer->addCommand(&_quadCommand);
// 物理引擎相關繪制邊界
#if CC_SPRITE_DEBUG_DRAW _customDebugDrawCommand.init(_globalZOrder); _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this); renderer->addCommand(&_customDebugDrawCommand); #endif //CC_SPRITE_DEBUG_DRAW } }

(1-2-2-1-1-1-1) 從代碼中可以看出,Sprite的draw函數里面並沒有做實際的渲染工作,而是用QuadCommand命令將渲染操作打包,加入到渲染隊列里面,在drawscene最后通過調用render()進行統一渲染;我們可以看看_quadCommand.init里面究竟做了什么,找到/cocos2d-x-3.2/cocos/renderer/CCQuadCommand.cpp:

void QuadCommand::init(float globalOrder, GLuint textureID, GLProgramState* glProgramState, BlendFunc blendType, V3F_C4B_T2F_Quad* quad, ssize_t quadCount, const Mat4 &mv)
{
    CCASSERT(glProgramState, "Invalid GLProgramState");
    CCASSERT(glProgramState->getVertexAttribsFlags() == 0, "No custom attributes are supported in QuadCommand");

    _globalOrder = globalOrder;

    _quadsCount = quadCount;
    _quads = quad;
    
    // 設置MV矩陣
    _mv = mv;

    if( _textureID != textureID || _blendType.src != blendType.src || _blendType.dst != blendType.dst || _glProgramState != glProgramState) {
        // 
        _textureID = textureID;
       // _blendType就是我們的BlendFunc混合函數
        _blendType = blendType;
        _glProgramState = glProgramState;
       
        // 生成材質ID
        generateMaterialID();
    }
}

(1-2-2-1-1-1-1-1) 我們在進入到generateMaterialID()函數里面看看:

void QuadCommand::generateMaterialID()
{

    if(_glProgramState->getUniformCount() > 0)
    {
        _materialID = QuadCommand::MATERIAL_ID_DO_NOT_BATCH;
    }
    else
    {
        int glProgram = (int)_glProgramState->getGLProgram()->getProgram();
        int intArray[4] = { glProgram, (int)_textureID, (int)_blendType.src, (int)_blendType.dst};

        _materialID = XXH32((const void*)intArray, sizeof(intArray), 0);
    }
}

從這里我們可以看出, 我們的材質ID(_materialID)最終是要由shader(glProgram)、混合類型(_blendType)、紋理ID(_textureID)組成的, 所以這三樣東西如果有誰不一樣的話,那就無法生成相同的材質ID,也就無法在同一 個批次里進行渲染了。

(1-2-2-1-2) 現在,我們回到(1-2-2-1-1-1)的draw函數, 通過上面將渲染指令初始化之后,就是將打包好的渲染命令添加到渲染隊列里面了.這里只需簡單調用renderer->addCommand(&_quadCommand);即可. 這樣,(1-2-2-1)處的drawscene函數中,visit通過調用派生類節點添加渲染指令到渲染隊列的工作已經完成了.接下來要做的就是做實際的渲染工作了.3.x版本與之前版本不同,是在drawscene最后通過調用render()函數進行統一渲染的,我們進入render()看一下,找到cocos2d-x-3.2/cocos/renderer/CCRenderer.cpp:

void Renderer::render()
{
    //Uncomment this once everything is rendered by new renderer
    //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //TODO setup camera or MVP
    _isRendering = true;
    
    if (_glViewAssigned)
    {
        // cleanup
        _drawnBatches = _drawnVertices = 0;

        //Process render commands
        //1. Sort render commands based on ID
        for (auto &renderqueue : _renderGroups)
        {
            renderqueue.sort();
        }
        visitRenderQueue(_renderGroups[0]);
        flush();
    }
    clean();
    _isRendering = false;
}

 從代碼可以看出,從Cocos2d-x3.0開始,Cocos2d-x引入了新的渲染流程,它不像2.x版本 直接在每一個node中的draw函數中直接調用OpenGL代碼進行圖形渲染,而是通過各種RenderCommand封裝起來,然后添加到一個 CommandQueue隊列里面去,而現在draw函數的作用就是在此函數中設置好相對應的RenderCommand參數,然后把此 RenderCommand添加到CommandQueue中。最后在每一幀結束時調用renderer函數進行渲染,在renderer函數中會根據 ID對RenderCommand進行排序,然后才進行渲染。

(1-2-2-1-2-1) 現在我們進入visitRenderQueue函數看看它做了什么動作:

 

void Renderer::visitRenderQueue(const RenderQueue& queue)
{
    ssize_t size = queue.size();
    
    for (ssize_t index = 0; index < size; ++index)
    {
        auto command = queue[index];
        auto commandType = command->getType();
    
if(RenderCommand::Type::QUAD_COMMAND == commandType) { flush3D(); auto cmd = static_cast<QuadCommand*>(command); //Batch quads
// 如果Quad數據量超過VBO的大小,那么調用繪制,將緩存的命令全部繪制 if(_numQuads + cmd->getQuadCount() > VBO_SIZE) { CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() < VBO_SIZE, "VBO is not big enough for quad data, please break the quad data down or use customized render command"); //Draw batched quads if VBO is full drawBatchedQuads(); }
       // 這個處理主要是把命令存入_batchedQuadCommands中,如果如果Quad數據量超過VBO的大小,那么調用繪制,將緩存的命令全部繪制.
       // 如果一直沒有超過VBO的大小,drawBatchedQuads繪制函數將在flush被調用時調用.
// 將命令緩存起來,先不調用繪制 _batchedQuadCommands.push_back(cmd); memcpy(_quads
+ _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
// 通過MV矩陣, 轉換成世界坐標 convertToWorldCoordinates(_quads
+ _numQuads, cmd->getQuadCount(), cmd->getModelView()); // 記錄下四邊形數量 _numQuads += cmd->getQuadCount(); } else if(RenderCommand::Type::GROUP_COMMAND == commandType) { flush(); int renderQueueID = ((GroupCommand*) command)->getRenderQueueID(); visitRenderQueue(_renderGroups[renderQueueID]); } else if(RenderCommand::Type::CUSTOM_COMMAND == commandType) { flush(); auto cmd = static_cast<CustomCommand*>(command); cmd->execute(); } else if(RenderCommand::Type::BATCH_COMMAND == commandType) { flush(); auto cmd = static_cast<BatchCommand*>(command); cmd->execute(); } else if (RenderCommand::Type::MESH_COMMAND == commandType) { flush2D(); auto cmd = static_cast<MeshCommand*>(command); if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID()) { flush3D(); cmd->preBatchDraw(); cmd->batchDraw(); _lastBatchedMeshCommand = cmd; } else { cmd->batchDraw(); } } else { CCLOGERROR("Unknown commands in renderQueue"); } } }

 

從代碼中,我們看到RenderCommand類型有QUAD_COMMAND,CUSTOM_COMMAND,BATCH_COMMAND,GROUP_COMMAND,MESH_COMMAND五種,OpenGL的API調用是在Renderer::drawBatchedQuads()、BatchCommand::execute()中。通過上面代碼的注釋,可以看到最常用的QUAD_COMMAND類型的渲染命令的處理過程.

(1-2-2-1-2-1-1) 如果Quad數據量超過VBO的大小(VBO_SIZE = 65536 / 6;), 則會調用drawBatchedQuads進行批量渲染:

 

void Renderer::drawBatchedQuads()
{
    //TODO we can improve the draw performance by insert material switching command before hand.

    int quadsToDraw = 0;
    int startQuad = 0;

    //Upload buffer to VBO
    if(_numQuads <= 0 || _batchedQuadCommands.empty())
    {
        return;
    }
    // 是否支持VAO if (Configuration::getInstance()->supportsShareableVAO())
    {
        //Set VBO data 綁定VBO數據, 激活緩沖區對象
        glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);

        // option 1: subdata
//        glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] );

        // option 2: data
//        glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW);

        // option 3: orphaning + glMapBuffer
     // 用數據分配和初始化緩沖區對象
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
// OPENGL 緩沖區對象(buffer object),允許應用程序顯式地指定把哪些數據存儲在圖形服務器或顯存中
     // 返回指向緩沖區的指針, 緩沖一經具體使用之后,只需要改變緩沖區的內容,即在glMapBuffer和glUnmapBuffer之間改變數據即可
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); memcpy(buf, _quads, sizeof(_quads[0])* (_numQuads)); glUnmapBuffer(GL_ARRAY_BUFFER); // 解除綁定 glBindBuffer(GL_ARRAY_BUFFER, 0); //Bind VAO 綁定VAO GL::bindVAO(_quadVAO); } else { #define kQuadSize sizeof(_quads[0].bl) glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _numQuads , _quads, GL_DYNAMIC_DRAW); GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX); // vertices glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices)); // colors glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors)); // tex coords glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords)); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]); } //Start drawing verties in batch for(const auto& cmd : _batchedQuadCommands) { auto newMaterialID = cmd->getMaterialID(); if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH) { //Draw quads if(quadsToDraw > 0) {
// 四邊形都可以由2個三角形組合而成,指定6個索引點(畫出2個GL_TRIANGLES) glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw
*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) ); _drawnBatches++; _drawnVertices += quadsToDraw*6; startQuad += quadsToDraw; quadsToDraw = 0; } //Use new material cmd->useMaterial(); _lastMaterialID = newMaterialID; } quadsToDraw += cmd->getQuadCount(); } //Draw any remaining quad if(quadsToDraw > 0) {
// 畫剩下的四邊形 glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw
*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) ); _drawnBatches++; _drawnVertices += quadsToDraw*6; } if (Configuration::getInstance()->supportsShareableVAO()) { //Unbind VAO 接除綁定VAO GL::bindVAO(0); } else { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } _batchedQuadCommands.clear(); _numQuads = 0; }

 

附注:5種渲染類型:

1. QUAD_COMMAND:QuadCommand類繪制精靈等。所有繪制圖片的命令都會調用到這里,處理這個類型命令的代碼就是繪制貼圖的openGL代碼。
2. CUSTOM_COMMAND:CustomCommand類自定義繪制,自己定義繪制函數,在調用繪制時只需調用已經傳進來的回調函數就可以,裁剪節點,繪制圖形節點都采用這個繪制,把繪制函數定義在自己的類里。這種類型的繪制命令不會在處理命令的時候調用任何一句openGL代碼,而是調用你寫好並設置給func的繪制函數。
3. BATCH_COMMAND:BatchCommand類批處理繪制,批處理精靈和粒子,其實它類似於自定義繪制,也不會再render函數中出現任何一句openGL函數。
4. GROUP_COMMAND:GroupCommand類繪制組,一個節點包括兩個以上繪制命令的時候,把這個繪制命令存儲到另外一個_renderGroups中的元素中,並把這個元素的指針作為一個節點存儲到_renderGroups[0]中。

5. MESH_COMMAND :

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM