最近在研究Cocos引擎的渲染流程,在這裏將其整個渲染流程進行一下梳理:node
梳理以前咱們要知道一些東西,就是咱們的Cocos引擎是經過使用OpenGL的一些API來進行渲染繪製的,因此若是咱們要完全理解Cocos引擎的渲染流程並想修改引擎底層渲染的相關內容,熟悉OpenGL是頗有必要的。ide
這裏先簡單說一下大概流程,Cocos3.x版本的渲染是將全部須要渲染的node先經過各類RenderCommand封裝起來,你先不用管RenderCommand是什麼,只須要記住它把咱們要渲染的node封裝起來了就行,而後引擎把這些RenderCommand添加到了一個隊列中存了起來,這個隊列叫CommandQueue,添加的時候順便對這些RenderCommand設置了一些參數,最後在每一幀結束時調用進行渲染,渲染前會根據ID對RenderCommand進行排序,而後再進行渲染。函數
好了接下來咱們來開始梳理引擎整個的渲染流程了:oop
首先,整個工程的渲染流程的入口在哪裏呢?優化
咱們打開工程文件目錄,在 platform\win32文件目錄下找到CCApplication-win3類文件,這裏要注意不一樣平臺的不同,好比mac平臺下是platform\mac目錄下的CCApplication-mac文件,根據咱們發佈的工程平臺的不一樣,這個CCApplication類文件也不一樣。整個渲染流程就在這個CCApplication類文件run()方法中開始,代碼以下:ui
[cpp] view plain copy print?this
int Application::run() spa
{ .net
...... 線程
director->mainLoop();//進入引擎的主循環
......
return 0;
}
int Application::run() { ...... director->mainLoop();//進入引擎的主循環 ...... return 0; }
這裏咱們要了解一個概念,就是cocos2dx整個工程是運行在一個單線程裏的,也就是咱們常常說的主線程,在主線程裏完成渲染、相關的定時器等等處理。注意Application::run()中的這句:
[cpp] view plain copy print?
director->mainLoop();
director->mainLoop();
這句代碼就是進入cocos2d-x的主循環了,這個主循環mainLoop()由導演負責維護,主線程mainloop()會不停地執行,理想狀態下每秒會調用60次。
那咱們看看CCDirector類裏的mainLoop()方法具體作了些什麼:
[cpp] view plain copy print?
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)//進入下一個主循環,也就是結束此次的主循環,就淨化,也就是一些後期處理
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene();//繪製屏幕
PoolManager::getInstance()->getCurrentPool()->clear();//釋放一些沒有用的對象,主要保件內存的合理管理
}
}
void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop)//進入下一個主循環,也就是結束此次的主循環,就淨化,也就是一些後期處理 { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (_restartDirectorInNextLoop) { _restartDirectorInNextLoop = false; restartDirector(); } else if (! _invalid) { drawScene();//繪製屏幕 PoolManager::getInstance()->getCurrentPool()->clear();//釋放一些沒有用的對象,主要保件內存的合理管理 } }
最開始我還疑惑爲何mainLoop()方法的類是DisplayLinkDirector而不是CCDirector,可是在CCDirector.cpp中咱們會找到以下代碼:
[cpp] view plain copy print?
static DisplayLinkDirector *s_SharedDirector = nullptr;
Director* Director::getInstance()
{
if (!s_SharedDirector)
{
s_SharedDirector = new (std::nothrow) DisplayLinkDirector();
CCASSERT(s_SharedDirector, "FATAL: Not enough memory");
s_SharedDirector->init();
}
return s_SharedDirector;
}
static DisplayLinkDirector *s_SharedDirector = nullptr; Director* Director::getInstance() { if (!s_SharedDirector) { s_SharedDirector = new (std::nothrow) DisplayLinkDirector(); CCASSERT(s_SharedDirector, "FATAL: Not enough memory"); s_SharedDirector->init(); } return s_SharedDirector; }
咱們能夠看到Director類返回的單例對象是一個DisplayLinkDirector類型的,因此這個導演實例要執行mainLoop()方法,這個方法天然是DisplayLinkDirector類裏的方法啦!
可是這是否是說明Director類就是DisplayLinkDirector類或繼承自DisplayLinkDirector類呢?千萬不要這樣想!這兩個類沒有半毛錢關係,咱們在CCDirector.h中看到以下代碼:
[cpp] view plain copy print?
class CC_DLL Director : public Ref
class CC_DLL Director : public Ref
能夠看出Director類是繼承自Ref類的,只是經過getInstance()方法返回的導演類的實例對象是DisplayLinkDirector類型的,CCDisplayLinkDirector類是CCDisplay的子類,從命名就應該能夠很清晰的知道它的用處。這裏雖然有點繞,但不要混淆哈!
好了,回過頭來,在DisplayLinkDirector::mainLoop()方法中我能夠看到這句代碼:
[cpp] view plain copy print?
void DisplayLinkDirector::mainLoop()
{
......
drawScene();
......
}
void DisplayLinkDirector::mainLoop() { ...... drawScene(); ...... }
mainloop()若是執行會調用drawScene(),經過drawScene()代碼就能夠實現場景的繪製了。
那咱們繼續看看drawScene()具體作了些什麼:
[cpp] view plain copy print?
void Director::drawScene()
{
......
if (_notificationNode)
{
_notificationNode->visit(_renderer, Mat4::IDENTITY, 0);
}
......
_renderer->render();
}
void Director::drawScene() { ...... if (_notificationNode) { _notificationNode->visit(_renderer, Mat4::IDENTITY, 0); } ...... _renderer->render(); }
Director::drawScene()作了好多事情,其餘的先不看,咱們主要關注這兩句:
[cpp] view plain copy print?
1._notificationNode->visit(_renderer, Mat4::IDENTITY, 0);
1._notificationNode->visit(_renderer, Mat4::IDENTITY, 0);
[cpp] view plain copy print?
2._renderer->render();
2._renderer->render();
先看第一句,這句_notificationNode->visit(_renderer, Mat4::IDENTITY, 0) ,這句實際上是進入了一個循環調用,具體要看CCNode.cpp:
[cpp] view plain copy print?
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
......
for( ; i < _children.size(); i++ )
{
auto node = _children.at(i);
if (node && node->_localZOrder < 0)
node->visit(renderer, _modelViewTransform, flags);
else
break;
}
......
this->draw(renderer, _modelViewTransform, flags);
......
}
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags) { ...... for( ; i < _children.size(); i++ ) { auto node = _children.at(i); if (node && node->_localZOrder < 0) node->visit(renderer, _modelViewTransform, flags); else break; } ...... this->draw(renderer, _modelViewTransform, flags); ...... }
這個函數有一個循環調用,咱們能夠看到auto node = _children.at(i);和node->visit(renderer, _modelViewTransform, flags);,這段代碼的意思是先獲取子節點,而後遞歸調用節點的visit()函數,到了沒有子節點的節點,執行了這句this->draw(renderer, _modelViewTransform, flags),開始調用draw()函數,那麼咱們接着看draw()函數代碼:
[cpp] view plain copy print?
void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags)
{
}
void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags) { }
裏面什麼都沒有啊,這是怎麼回事?其實這個draw()函數是個虛函數,因此它執行時執行的是該子節點類的draw()函數。那麼咱們分別看DrawNode::draw()、Sprite::draw():
[cpp] view plain copy print?
void DrawNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
if(_bufferCount)
{
......
renderer->addCommand(&_customCommand);
}
if(_bufferCountGLPoint)
{
......
renderer->addCommand(&_customCommandGLPoint);
}
if(_bufferCountGLLine)
{
......
renderer->addCommand(&_customCommandGLLine);
}
}
void DrawNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if(_bufferCount) { ...... renderer->addCommand(&_customCommand); } if(_bufferCountGLPoint) { ...... renderer->addCommand(&_customCommandGLPoint); } if(_bufferCountGLLine) { ...... renderer->addCommand(&_customCommandGLLine); } }
[cpp] view plain copy print?
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
......
if(_insideBounds)
{
......
renderer->addCommand(&_trianglesCommand);
}
}
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { ...... if(_insideBounds) { ...... renderer->addCommand(&_trianglesCommand); } }
咱們能夠看到在在這些子類的draw()函數都執行了renderer->addCommand()代碼,這是向RenderQueue中添加RenderCommand,在添加時順便對RenderCommand進行了一些參數設置,固然有的類的draw()不是向RenderQueue中添加RenderCommand,而是直接使用OpenGL的API直接進行渲染,或者作一些其餘的事情。
當Director::drawScene()循環調用完全部子節點的visit()方法而且執行完draw()方法,即向RenderQueue中添加完RenderCommand後,咱們就看看接下來進行渲染的Renderer::render() 函數都作了些什麼:
[cpp] view plain copy print?
void Renderer::render()
{
_isRendering = true;
if (_glViewAssigned)
{
for (auto &renderqueue : _renderGroups)
{
renderqueue.sort();
}
visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;
}
void Renderer::render() { _isRendering = true; if (_glViewAssigned) { for (auto &renderqueue : _renderGroups) { renderqueue.sort(); } visitRenderQueue(_renderGroups[0]); } clean(); _isRendering = false; }
看到「renderqueue.sort()",這是根據ID先對全部RenderCommand進行排序,而後才進行渲染,「visitRenderQueue( _renderGroups[0])」就是來進行渲染的。
那麼咱們接着看看void Renderer::visitRenderQueue(const RenderQueue& queue)的代碼:
[cpp] view plain copy print?
void Renderer::visitRenderQueue(RenderQueue& queue)
{
queue.saveRenderState();
const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);
if (zNegQueue.size() > 0)
{
if(_isDepthTestFor2D)
{
glEnable(GL_DEPTH_TEST);
glDepthMask(true);
glEnable(GL_BLEND);
RenderState::StateBlock::_defaultState->setDepthTest(true);
RenderState::StateBlock::_defaultState->setDepthWrite(true);
RenderState::StateBlock::_defaultState->setBlend(true);
}
else
{
glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glEnable(GL_BLEND);
RenderState::StateBlock::_defaultState->setDepthTest(false);
RenderState::StateBlock::_defaultState->setDepthWrite(false);
RenderState::StateBlock::_defaultState->setBlend(true);
}
for (auto it = zNegQueue.cbegin(); it != zNegQueue.cend(); ++it)
{
proce***enderCommand(*it);
}
flush();
}
void Renderer::visitRenderQueue(RenderQueue& queue) { queue.saveRenderState(); const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG); if (zNegQueue.size() > 0) { if(_isDepthTestFor2D) { glEnable(GL_DEPTH_TEST); glDepthMask(true); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(true); RenderState::StateBlock::_defaultState->setDepthWrite(true); RenderState::StateBlock::_defaultState->setBlend(true); } else { glDisable(GL_DEPTH_TEST); glDepthMask(false); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(false); RenderState::StateBlock::_defaultState->setDepthWrite(false); RenderState::StateBlock::_defaultState->setBlend(true); } for (auto it = zNegQueue.cbegin(); it != zNegQueue.cend(); ++it) { proce***enderCommand(*it); } flush(); }
在visitRenderQueue()方法中我咱們看到這一行代碼:
[cpp] view plain copy print?
proce***enderCommand(*it);
proce***enderCommand(*it);
這是幹什麼的呢?這句代碼就是進一步進入渲染流程的,咱們看一下proce***enderCommand()它作了什麼:
[cpp] view plain copy print?
void Renderer::proce***enderCommand(RenderCommand* command)
{
auto commandType = command->getType();
if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
{
......
drawBatchedTriangles();
......
}
else if ( RenderCommand::Type::QUAD_COMMAND == commandType )
{
......
drawBatchedQuads();
......
}
else if (RenderCommand::Type::MESH_COMMAND == commandType)
{
......
auto cmd = static_cast<MeshCommand*>(command);
......
cmd->execute();
......
}
......
}
void Renderer::proce***enderCommand(RenderCommand* command) { auto commandType = command->getType(); if( RenderCommand::Type::TRIANGLES_COMMAND == commandType) { ...... drawBatchedTriangles(); ...... } else if ( RenderCommand::Type::QUAD_COMMAND == commandType ) { ...... drawBatchedQuads(); ...... } else if (RenderCommand::Type::MESH_COMMAND == commandType) { ...... auto cmd = static_cast<MeshCommand*>(command); ...... cmd->execute(); ...... } ...... }
咱們能夠看到,在這裏,根據渲染類型的不一樣,會調用不一樣的函數,這些函數裏有OpenGL的API,沒錯,這些函數來進行渲染的。好比TRIANGLES_COMMAND類型中調用了drawBatchedTriangles(),QUAD_COMMAND類型中調用了drawBatchedQuads(),MESH_COMMAND類型中調用了MeshCommand::execute(),等等。
舉個例子,咱們來看下drawBatchedTriangles()方法:
[cpp] view plain copy print?
void Renderer::drawBatchedTriangles()
{
......
if (Configuration::getInstance()->supportsShareableVAO())
{
......}
else
{
......
// 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]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * _filledIndex, _indices, GL_STATIC_DRAW);
}
......
}
void Renderer::drawBatchedTriangles() { ...... if (Configuration::getInstance()->supportsShareableVAO()) { ......} else { ...... // 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]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * _filledIndex, _indices, GL_STATIC_DRAW); } ...... }
能夠看到該方法中調用了不少OpenGL的API,這些方法就是整個渲染流程最後進行渲染的環節。
好了,以上即是Cocos引擎的整個的渲染流程了。
最後用一個流程圖對以上內容作一下總結,話說這張圖我真的是很用心畫的,改了好多遍最後優化到如今這個樣子給你們看,但願對你們有幫助:
以上。