• 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
    ",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 :

  • 相关阅读:
    android java epson串口打印机
    CMake交叉编译配置
    【BIRT】汉化设计器
    数据仓库理论
    【Excel】多条件查找
    【Excel】定位条件快速将空值替换为指定值
    【DataStage】使用Sequence Job报错:CopyOfseq_ld..JobControl (fatal error from @Coordinator): Sequence job (restartable) will abort due to previous unrecoverable errors
    【Excel】数据字典制作
    【PMP】关键路径法与关键链法
    【PMP】易混淆知识点
  • 原文地址:https://www.cnblogs.com/yyxt/p/5514412.html
Copyright © 2020-2023  润新知