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)这里可以看出,在入口函数中,首先创建了一个AppDeletegate对象,AppDeletegate继承 自CCApplication,在创建APPDeletegate对象的时候就会隐式调用CCApplication构造函数,在这个构造函数里边会将AppDeletegate的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-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;
    }

    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 :

时间: 2024-10-07 02:50:50

cocos2d-x游戏引擎核心(3.x)----启动渲染流程的相关文章

游戏引擎中三大及时光照渲染方法介绍(以unity3d为例)

(转)游戏引擎中三大及时光照渲染方法介绍(以unity3d为例) 重要:在目前市面上常见的游戏引擎中,主要采用以下三种灯光实现方式: 顶点照明渲染路径细节 Vertex Lit Rendering Path Details 正向渲染路径细节 Forward Rendering Path Details 延迟光照渲染路径的细节 Deferred Lighting Rendering Path Details 以unity3d为例,以下将详细讲解三种灯光渲染方式的实现.原理及缺陷. 顶点照明渲染路径

cocos2d-x游戏引擎核心之四——绘图原理和绘图技巧

一.OpenGL基础 游戏引擎是对底层绘图接口的包装,Cocos2d-x 也一样,它是对不同平台下 OpenGL 的包装.OpenGL 全称为 Open Graphics Library,是一个开放的.跨平台的高性能图形接口.OpenGL ES 则是 OpenGL 在移动设备上的衍生版本,具备与 OpenGL 一致的结构,包含了常用的图形功能.Cocos2d-x 就是一个基于 OpenGL 的游戏引擎,因此它的绘图部分完全由 OpenGL 实现.OpenGL 是一个基于 C 语言的三维图形 AP

游戏引擎架构逐层分析之渲染引擎篇

既然说到渲染引擎,那就不得不提到Ogre,OSG这两个了,OSG我折腾了两年,Ogre折腾了两年,说是我折腾他们,不如说被他们折磨,这个引擎的复杂度就不说了,相信大家都知道. 上面这个是渲染引擎的第一个组成部分,说是这一层主要实现渲染的能力和丰富度.可以看出来,分成了几个部分,其中Graphics Device Interface没什么说的,基本就是系统提供的操作显卡的接口了. 目前维护的游戏基本没有什么光照系统,后边可以应用的光照系统是什么呢?怎么给场景打光?是否需要动态光照呢?比如太阳,再比

游戏引擎

游戏引擎概述 游戏引擎是指一些已编写好的可编辑电脑游戏系统或者一些交互式实时图像应用程序的核心组件.这些系统为游戏设计者提供各种编写游戏所需的各种工具,其目的在于让游戏设计者能容易和快速地做出游戏程式而不用由零开始.大部分都支持多种操作平台,如Linux.Mac OS X.微软Windows.游戏引擎包含以下系统:渲染引擎(即“渲染器”,含二维图像引擎和三维图像引擎).物理引擎.碰撞检测系统.音效.脚本引擎.电脑动画.人工智能.网络引擎以及场景管理. 详细介绍,请猛戳这里...... 常见的游戏

主流游戏引擎分析 【端游 、页游 、手游 解析】

该分享仅供参考,目的是提升大家对游戏引擎方面的一些认知.文档中部分内容收集于互联网,若有内容不准确,还请告知. 关于本文PPT文档:github 一.介绍 游戏引擎是指一些已编写好的可编辑电脑游戏系统或者一些互交式实时图像应用程序的核心组件.这些系统为游戏设计者提供各种编写游戏所需的各种工具,其目的在于让游戏设计者能容易和快速地做出游戏程序而不用由零开始.大部分都支持多种操作系统平台,如Linux.Mac OS X.微软Windows.游戏引擎包含以下系统:渲染引擎(即"渲染器",含二

iOS cocos2d游戏引擎的了解之一

ios游戏引擎之Cocos2d(一) cocos2d是一个免费开源的ios游戏开发引擎,并且完全采用object-c进行编写,这对于已经用惯object-c进行ios应用开发的童鞋来说非常容易上手.这些也是我推荐使用cocos2d进行ios游戏开发的原因,当然从字面上已经可以开出来,这是一款专注于"2d"游戏的开发引擎,您也可以自己编写3d渲染代码或者使用第三方的解决方案,在cocos2d里加载显示3d模型.此外对于3d,也可以选用cocos3d来进行游戏开发.好了,废话不多说,还是先

游戏引擎不仅是代码,更多的是完善的工具

从洗脑开始 记得若干年前,在做公司引擎研发的时候,时常会念到的一句话:引擎不仅是代码,更多的是完善的工具.当时只是用这句话还激励自己,找准引擎开发的原则和位置. 而实际上,对这句话的理解甚少.时隔多年,这句话油然在耳,伴随我左右 亲身体会 后来,引擎项目砍掉了,进入了页游产品的开发. 在这个产品开发的第一周,我们就面临着动画和场景编辑问题,在脑海中第一时间浮现出的,依然是这句话.于是,我们花了两个星期来做了一个简单的动画编辑器和场景编辑器.动画编辑器只有简单的图片导入,和锚点设置功能(因为怪物大

游戏引擎架构

游戏编程分为游戏逻辑和游戏引擎.游戏引擎是一套可重复利用的底层框架.包括渲染引擎.声音引擎.网络引擎等等.现提供一套游戏架构,从底层向上分别为: 硬件.驱动.操作系统. 第三方软件开发包:DirectX.OpenGL,Boost.STL库,Granny.Havok.Animation等. 平台独立层:集合.迭代器.文件系统.网络传输层.线程库.物理碰撞包裹类. 核心系统:模块启动终止.断言.单元测试.内存分配.数学库.调试用打印及日志.性能剖析.引擎配置.异步文件. 资源(游戏资产):三维模型资

lufylegend游戏引擎

lufylegend游戏引擎介绍:click 这个链接我觉得已经很详细的介绍了这个引擎. 所以以下我只说说一些简单的游戏代码过程. 首先从canvas做游戏叙述起: 这是一个让人很熟悉的简单小游戏,网上到处都是这个小游戏代码,所以就简单说说: HTML代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>简单游戏&