cocos渲染流程
最近在研究Cocos引擎的渲染流程,在這里將其整個渲染流程進行一下梳理:
梳理之前我們要知道一些東西,就是我們的Cocos引擎是通過使用OpenGL的一些API來進行渲染繪制的,所以如果我們要徹底理解Cocos引擎的渲染流程并想修改引擎底層渲染的相關內容,熟悉OpenGL是很有必要的。
這里先簡單說一下大概流程,Cocos3.x版本的渲染是將所有需要渲染的node先通過各種RenderCommand封裝起來,你先不用管RenderCommand是什么,只需要記住它把我們要渲染的node封裝起來了就行,然后引擎把這些RenderCommand添加到了一個隊列中存了起來,這個隊列叫CommandQueue,添加的時候順便對這些RenderCommand設置了一些參數,最后在每一幀結束時調用進行渲染,渲染前會根據ID對RenderCommand進行排序,然后再進行渲染。
?
?
好了接下來我們來開始梳理引擎整個的渲染流程了:
首先,整個工程的渲染流程的入口在哪里呢?
我們打開工程文件目錄,在?platform\win32文件目錄下找到CCApplication-win3類文件,這里要注意不同平臺的不一樣,比如mac平臺下是platform\mac目錄下的CCApplication-mac文件,根據我們發布的工程平臺的不同,這個CCApplication類文件也不同。整個渲染流程就在這個CCApplication類文件run()方法中開始,代碼如下:
[cpp] view plain copy print?
int?Application::run()??
{?????
????......?????????
????director->mainLoop();//進入引擎的主循環??
????......??????
????return?0;??
}??
這里我們要了解一個概念,就是cocos2dx整個工程是運行在一個單線程里的,也就是我們經常說的主線程,在主線程里完成渲染、相關的定時器等等處理。注意Application::run()中的這句:
[cpp] view plain copy print?
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();//釋放一些沒有用的對象,主要保件內存的合理管理???
????}??
}??
最開始我還疑惑為什么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;??
}??
我們可以看到Director類返回的單例對象是一個DisplayLinkDirector類型的,所以這個導演實例要執行mainLoop()方法,這個方法自然是DisplayLinkDirector類里的方法啦!
但是這是不是說明Director類就是DisplayLinkDirector類或繼承自DisplayLinkDirector類呢?千萬不要這樣想!這兩個類沒有半毛錢關系,我們在CCDirector.h中看到如下代碼:
[cpp] view plain copy print?
class?CC_DLL?Director?:?public?Ref??
可以看出Director類是繼承自Ref類的,只是通過getInstance()方法返回的導演類的實例對象是DisplayLinkDirector類型的,CCDisplayLinkDirector類是CCDisplay的子類,從命名就應該可以很清晰的知道它的用處。這里雖然有點繞,但不要混淆哈!
好了,回過頭來,在DisplayLinkDirector::mainLoop()方法中我可以看到這句代碼:
[cpp] view plain copy print?
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();??
}??
Director::drawScene()做了好多事情,其他的先不看,我們主要關注這兩句:
[cpp] view plain copy print?
1._notificationNode->visit(_renderer,?Mat4::IDENTITY,?0);??
[cpp] view plain copy print?
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);??
????????......??
}??
這個函數有一個循環調用,我們可以看到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)??
{??
}??
里面什么都沒有啊,這是怎么回事?其實這個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);??
????}??
}??
[cpp] view plain copy print?
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;??
}??
看到“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();??
}??
在visitRenderQueue()方法中我我們看到這一行代碼:
[cpp] view plain copy print?
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();??
????????......??
????}??
????......??
}??
我們可以看到,在這里,根據渲染類型的不同,會調用不同的函數,這些函數里有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);??
??????}??
???????......??
}??
可以看到該方法中調用了很多OpenGL的API,這些方法就是整個渲染流程最后進行渲染的環節。
?
好了,以上便是Cocos引擎的整個的渲染流程了。
最后用一個流程圖對以上內容做一下總結,話說這張圖我真的是很用心畫的,改了好多遍最后優化到現在這個樣子給大家看,希望對大家有幫助:
?
?
?
?
?
?
?
?
以上。
轉載于:https://blog.51cto.com/12525470/1934434
總結
- 上一篇: struts过滤器和拦截器的区别
- 下一篇: Storm概念学习系列之Topology