cocos2d-x游戏引擎核心(3.x)----启动渲染流程
(1) 首先,這里以win32平臺下為例子.win32下游戲的啟動都是從win32目錄下main文件開始的,即是游戲的入口函數(shù),如下:
#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)這里可以看出,在入口函數(shù)中,首先創(chuàng)建了一個AppDelegate對象,AppDelegate繼承 自CCApplication,在創(chuàng)建APPDelegate對象的時候就會隱式調(diào)用CCApplication構(gòu)造函數(shù),在這個構(gòu)造函數(shù)里邊會將AppDelegate的this指針傳遞給全局共享對象sm_pSharedApplication,如下:
Application::Application()//初始化win32應(yīng)用程序?qū)ο? : _instance(nullptr) , _accelTable(nullptr) {_instance = GetModuleHandle(nullptr);
// 用于控制幀數(shù)的計數(shù)值_animationInterval.QuadPart = 0;CC_ASSERT(! sm_pSharedApplication);
// 全局共享對象sm_pSharedApplication = this; }
(1-2) 接下來調(diào)用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.// 執(zhí)行AppDeletegate重載的applicationDidFinishLaunching函數(shù)if (!applicationDidFinishLaunching()){return 0;}auto director = Director::getInstance();auto glview = director->getOpenGLView();// Retain glview to avoid glview being released in the while loopglview->retain();while(!glview->windowShouldClose()){QueryPerformanceCounter(&nNow);if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart){nLast.QuadPart = nNow.QuadPart;// 主循環(huán),每幀調(diào)用director->mainLoop();glview->pollEvents();}else{Sleep(0);}}// Director should still do a cleanup if the window was closed manually.if (glview->isOpenGLReady()){// 結(jié)束,執(zhí)行清理工作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 directorauto director = Director::getInstance();auto glview = director->getOpenGLView();if(!glview) {// 創(chuàng)建glview對象, 這里采用默認的分辨率先創(chuàng)建出游戲窗口glview = GLView::create("My Game");
// 這里設(shè)置了和OpenGL相關(guān)的一些信息director->setOpenGLView(glview);}// turn on display FPSdirector->setDisplayStats(true);// set FPS. the default value is 1.0/60 if you don't call thisdirector->setAnimationInterval(1.0 / 60);// create a scene. it's an autorelease object
// 創(chuàng)建場景auto scene = HelloWorld::createScene();// run 運行場景director->runWithScene(scene);return true; }
(1-2-1-1)?可以看到applicationDidFinishLaunching函數(shù)里面設(shè)置了glview對象之后,就開始運行場景,可以進入GLView::create中看其究竟是如何創(chuàng)建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_BEGINclass 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;
// 交換buffervirtual void swapBuffers() override;
// 設(shè)置窗口大小virtual void setFrameSize(float width, float height) override;
// 設(shè)置輸入法狀態(tài)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 callbacksvoid 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 2float _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);// 獲取可見區(qū)域的原點和大小virtual Size getVisibleSize() const;virtual Vec2 getVisibleOrigin() const;virtual Rect getVisibleRect() const;
//設(shè)置設(shè)計的size,當(dāng)需要適配多種設(shè)備時,可以用這個函數(shù)定義邏輯坐標,cocos2dx會自動將邏輯坐標轉(zhuǎn)化成實際坐標,這樣一樣的代碼可以適配各種設(shè)備分辨率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: */
// 觸摸處理函數(shù),可以重載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 namestd::string _viewName;float _scaleX;float _scaleY;ResolutionPolicy _resolutionPolicy; };// end of platform group /// @} NS_CC_END#endif /* __CCGLVIEWPROTOCOL_H__ */
以看到CCEGLView和GLViewProtocol是顯示窗口,負責(zé)窗口級別的功能管理和實現(xiàn), 包括:坐標和縮放管理, 畫圖工具,按鍵事件;
(1-2-1-2) 創(chuàng)建glview對象之后,導(dǎo)演類Director就把glview設(shè)置進游戲,其中包括很多配置信息, 如設(shè)置屏幕大小適配相關(guān)的函數(shù)getDesignResolutionSize, 如下:
void Director::setOpenGLView(GLView *openGLView) {CCASSERT(openGLView, "opengl view should not be null");if (_openGLView != openGLView){// Configuration. Gather GPU infoConfiguration *conf = Configuration::getInstance();conf->gatherGPUInfo();CCLOG("%s\n",conf->getInfo().c_str());if(_openGLView)_openGLView->release();_openGLView = openGLView;_openGLView->retain();// set size 設(shè)置屏幕大小適配相關(guān)的函數(shù)_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() {// 如果使用VAOif(Configuration::getInstance()->supportsShareableVAO()){
// 初始化VAO和VBOsetupVBOAndVAO();}else{
// 初始化VBOsetupVBO();} }
?
(1-2-1-2-1-2-1) 進入setupVBOAndVAO和setupVBO, 開始調(diào)用OpenGL API進行頂點數(shù)據(jù)指定,具體意義參見基于Cocos2d-x學(xué)習(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里面創(chuàng)建場景之后,就調(diào)用director->mainLoop();開始游戲主循環(huán)了.我們進入mainLoop看它做了什么, win32下我們找到cocos2d-x-3.2/cocos/base/CCDirector.cpp:
void DisplayLinkDirector::mainLoop() {if (_purgeDirectorInNextLoop){_purgeDirectorInNextLoop = false;// 主循環(huán)結(jié)束,清除工作purgeDirector();}else if (! _invalid){
// 渲染場景drawScene();// release the objects
// 釋放對象:內(nèi)存池里之前通過autorelease加入的對象引用計數(shù)減 1.PoolManager::getInstance()->getCurrentPool()->clear();} }
mainLoop主要完成三個動作:
1?判斷是否需要釋放 CCDirector,如果需要,則刪除 CCDirector 占用的資源。通常,游戲結(jié)束時才會執(zhí)行這個步驟。
2?調(diào)用 drawScene()方法,繪制當(dāng)前場景并進行其他必要的處理。
3?彈出自動回收池,使得這一幀被放入自動回收池的對象全部釋放。
(1-2-2-1)?由此可見,mainLoop()把內(nèi)存管理以外的操作都交給了 drawScene()方法,因此關(guān)鍵的步驟都在 drawScene()方法之中。下面是 drawScene()方法的實現(xiàn):
// 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 #533if (! _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){
// 如果有,設(shè)置下一個場景setNextScene();}
// 保存原來的模型視圖(ModelView)矩陣pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);// draw the sceneif (_runningScene){
// 開始繪制場景_runningScene->visit(_renderer, Mat4::IDENTITY, false);
// 事件分發(fā)_eventDispatcher->dispatchEvent(_eventAfterVisit);}// draw the notifications nodeif (_notificationNode){
// 處理通知節(jié)點_notificationNode->visit(_renderer, Mat4::IDENTITY, false);}if (_displayStats){showStats();}// 開始渲染場景_renderer->render();_eventDispatcher->dispatchEvent(_eventAfterDraw);popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);_totalFrames++;// swap buffers 交換緩沖區(qū)if (_openGLView){_openGLView->swapBuffers();}if (_displayStats){calculateMPF();} }
可以發(fā)現(xiàn)drawScene主要用于處理 OpenGL 和一些細節(jié),如計算 FPS、幀間時間差等,這里我們主要進行了以下 3 個操作。
1 調(diào)用了定時調(diào)度器的 update 方法,引發(fā)定時器事件。
2 如果場景需要被切換,則調(diào)用?setNextScene 方法,在顯示場景前切換場景。
3 調(diào)用當(dāng)前場景的 visit 方法,將當(dāng)前場景加入渲染隊列,并通過render統(tǒng)一渲染。
(1-2-2-1-1)?我們進入到visit方法里面,看它怎樣把每一個節(jié)點添加到渲染隊列, 這里我們找到/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;}// 設(shè)置_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 itDirector* 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 < 0for( ; i < _children.size(); i++ ){auto node = _children.at(i);if ( node && node->_localZOrder < 0 )node->visit(renderer, _modelViewTransform, flags);elsebreak;}// self drawthis->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) 對節(jié)點的所有孩子排序,通過調(diào)用draw函數(shù),首先繪制ZOrder<0的節(jié)點,在繪制自身,最后繪制ZOrder>0的節(jié)點. 我們進入draw看看它做些什么. 注意,visit和draw都是虛函數(shù), 以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);// 物理引擎相關(guān)繪制邊界 #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函數(shù)里面并沒有做實際的渲染工作,而是用QuadCommand命令將渲染操作打包,加入到渲染隊列里面,在drawscene最后通過調(diào)用render()進行統(tǒng)一渲染;我們可以看看_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;// 設(shè)置MV矩陣_mv = mv;if( _textureID != textureID || _blendType.src != blendType.src || _blendType.dst != blendType.dst || _glProgramState != glProgramState) {// _textureID = textureID;// _blendType就是我們的BlendFunc混合函數(shù)_blendType = blendType;_glProgramState = glProgramState;// 生成材質(zhì)ID generateMaterialID();} }(1-2-2-1-1-1-1-1)?我們在進入到generateMaterialID()函數(shù)里面看看:
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);} }從這里我們可以看出, 我們的材質(zhì)ID(_materialID)最終是要由shader(glProgram)、混合類型(_blendType)、紋理ID(_textureID)組成的, 所以這三樣?xùn)|西如果有誰不一樣的話,那就無法生成相同的材質(zhì)ID,也就無法在同一 個批次里進行渲染了。
(1-2-2-1-2) 現(xiàn)在,我們回到(1-2-2-1-1-1)的draw函數(shù), 通過上面將渲染指令初始化之后,就是將打包好的渲染命令添加到渲染隊列里面了.這里只需簡單調(diào)用renderer->addCommand(&_quadCommand);即可. 這樣,(1-2-2-1)處的drawscene函數(shù)中,visit通過調(diào)用派生類節(jié)點添加渲染指令到渲染隊列的工作已經(jīng)完成了.接下來要做的就是做實際的渲染工作了.3.x版本與之前版本不同,是在drawscene最后通過調(diào)用render()函數(shù)進行統(tǒng)一渲染的,我們進入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 IDfor (auto &renderqueue : _renderGroups){renderqueue.sort();}visitRenderQueue(_renderGroups[0]);flush();}clean();_isRendering = false; }?從代碼可以看出,從Cocos2d-x3.0開始,Cocos2d-x引入了新的渲染流程,它不像2.x版本 直接在每一個node中的draw函數(shù)中直接調(diào)用OpenGL代碼進行圖形渲染,而是通過各種RenderCommand封裝起來,然后添加到一個 CommandQueue隊列里面去,而現(xiàn)在draw函數(shù)的作用就是在此函數(shù)中設(shè)置好相對應(yīng)的RenderCommand參數(shù),然后把此 RenderCommand添加到CommandQueue中。最后在每一幀結(jié)束時調(diào)用renderer函數(shù)進行渲染,在renderer函數(shù)中會根據(jù) ID對RenderCommand進行排序,然后才進行渲染。
(1-2-2-1-2-1) 現(xiàn)在我們進入visitRenderQueue函數(shù)看看它做了什么動作:
?
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數(shù)據(jù)量超過VBO的大小,那么調(diào)用繪制,將緩存的命令全部繪制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數(shù)據(jù)量超過VBO的大小,那么調(diào)用繪制,將緩存的命令全部繪制.
// 如果一直沒有超過VBO的大小,drawBatchedQuads繪制函數(shù)將在flush被調(diào)用時調(diào)用.// 將命令緩存起來,先不調(diào)用繪制_batchedQuadCommands.push_back(cmd);memcpy(_quads + _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
// 通過MV矩陣, 轉(zhuǎn)換成世界坐標convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView());// 記錄下四邊形數(shù)量_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調(diào)用是在Renderer::drawBatchedQuads()、BatchCommand::execute()中。通過上面代碼的注釋,可以看到最常用的QUAD_COMMAND類型的渲染命令的處理過程.
(1-2-2-1-2-1-1) 如果Quad數(shù)據(jù)量超過VBO的大小(VBO_SIZE = 65536 / 6;), 則會調(diào)用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 VBOif(_numQuads <= 0 || _batchedQuadCommands.empty()){return;}// 是否支持VAOif (Configuration::getInstance()->supportsShareableVAO()){//Set VBO data 綁定VBO數(shù)據(jù), 激活緩沖區(qū)對象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// 用數(shù)據(jù)分配和初始化緩沖區(qū)對象glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
// OPENGL 緩沖區(qū)對象(buffer object),允許應(yīng)用程序顯式地指定把哪些數(shù)據(jù)存儲在圖形服務(wù)器或顯存中
// 返回指向緩沖區(qū)的指針, 緩沖一經(jīng)具體使用之后,只需要改變緩沖區(qū)的內(nèi)容,即在glMapBuffer和glUnmapBuffer之間改變數(shù)據(jù)即可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);// verticesglVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices));// colorsglVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));// tex coordsglVertexAttribPointer(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 batchfor(const auto& cmd : _batchedQuadCommands){auto newMaterialID = cmd->getMaterialID();if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH){//Draw quadsif(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 materialcmd->useMaterial();_lastMaterialID = newMaterialID;}quadsToDraw += cmd->getQuadCount();}//Draw any remaining quadif(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 接除綁定VAOGL::bindVAO(0);}else{glBindBuffer(GL_ARRAY_BUFFER, 0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);}_batchedQuadCommands.clear();_numQuads = 0; }
?
附注:5種渲染類型:
1. QUAD_COMMAND:QuadCommand類繪制精靈等。所有繪制圖片的命令都會調(diào)用到這里,處理這個類型命令的代碼就是繪制貼圖的openGL代碼。
2. CUSTOM_COMMAND:CustomCommand類自定義繪制,自己定義繪制函數(shù),在調(diào)用繪制時只需調(diào)用已經(jīng)傳進來的回調(diào)函數(shù)就可以,裁剪節(jié)點,繪制圖形節(jié)點都采用這個繪制,把繪制函數(shù)定義在自己的類里。這種類型的繪制命令不會在處理命令的時候調(diào)用任何一句openGL代碼,而是調(diào)用你寫好并設(shè)置給func的繪制函數(shù)。
3. BATCH_COMMAND:BatchCommand類批處理繪制,批處理精靈和粒子,其實它類似于自定義繪制,也不會再render函數(shù)中出現(xiàn)任何一句openGL函數(shù)。
4. GROUP_COMMAND:GroupCommand類繪制組,一個節(jié)點包括兩個以上繪制命令的時候,把這個繪制命令存儲到另外一個_renderGroups中的元素中,并把這個元素的指針作為一個節(jié)點存儲到_renderGroups[0]中。
5. MESH_COMMAND :
?
轉(zhuǎn)載于:https://www.cnblogs.com/yyxt/p/5514412.html
總結(jié)
以上是生活随笔為你收集整理的cocos2d-x游戏引擎核心(3.x)----启动渲染流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Leetcode刷题记录[python]
- 下一篇: JavaScript的DOM操作