cocos2d-x的渲染流程及原理
环境: cocos3.10 Xcode
UI元素的渲染流程图示:
1. 从main进入到Application:run中,该方法下有个while循环,用于处理设定的每帧(FPS)刷新相关
{// ... glview->retain(); while (!glview->windowShouldClose()) { director->mainLoop(); } return 0; }
2. mainLoop做的事情如下:
void Director::mainLoop() { if (! _invalid) { // 绘制场景 drawScene(); // 自动内存释放相关,每帧结束后,检测释放掉内存池中引用计数为1的节点 PoolManager::getInstance()->getCurrentPool()->clear(); } }
3.绘制场景drawScene的主要代码:
void Director::drawScene() { // 渲染当前运行场景 if (_runningScene) { //clear draw stats _renderer->clearDrawStats(); //render the scene _openGLView->renderScene(_runningScene, _renderer); _eventDispatcher->dispatchEvent(_eventAfterVisit); } }
4. 通过renderScene会进入到Scene::render()中,它是处理渲染相关的主要接口:
void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount) { auto director = Director::getInstance(); Camera* defaultCamera = nullptr; // 转换坐标系,将ui元素相对坐标转换为世界坐标,原因在于OpenGL ES坐标系与cocos世界坐标系一致 const auto& transform = getNodeToParentTransform(); for (const auto& camera : getCameras()) { if (!camera->isVisible()) continue; Camera::_visitingCamera = camera; if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT) { defaultCamera = Camera::_visitingCamera; } // 遍历场景中UI树,会进入到Node::visit中,进行UI树遍历 visit(renderer, transform, 0); // 绘制,会进入到Renderer::render中,执行绘制 renderer->render(); // camera->restore(); // for (unsigned int i = 0; i < multiViewCount; ++i) director->popProjectionMatrix(i); } Camera::_visitingCamera = nullptr; }
5. visit主要用于UI树的遍历,遍历的规则使用的是中序(in-order)深度优先算法。即:
1. 遍历左边的子节点(小于0)
2. 遍历根节点(等于0)
3. 遍历右边的子节点(大于0)
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags) { // 如果不可见,则不再绘制 if (!_visible) { return; }
bool visibleByCamera = isVisitableByVisitingCamera(); int i = 0; if(!_children.empty()) {
// 按照localZOrder排序,若相同则按照UI树顺序排序 sortAllChildren(); // 遍历 localZOrder < 0 for(auto size = _children.size(); i < size; ++i) { auto node = _children.at(i); if (node && node->_localZOrder < 0) node->visit(renderer, _modelViewTransform, flags); else break; } // 遍历自身 if (visibleByCamera) this->draw(renderer, _modelViewTransform, flags); // 遍历localZOrder > 0
for(auto it=_children.cbegin()+i, itCend = _children.cend(); it != itCend; ++it) (*it)->visit(renderer, _modelViewTransform, flags); } else if (visibleByCamera) { this->draw(renderer, _modelViewTransform, flags); } _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); }
而遍历的方法主要通过sortAllChildren方法实现,比较的是localZOrder。
// sortAllChildren void Node::sortAllChildren() { if (_reorderChildDirty) { sortNodes(_children); _reorderChildDirty = false; } } // sortNodes static void sortNodes(cocos2d::Vector<_T*>& nodes) { static_assert(std::is_base_of<Node, _T>::value, "Node::sortNodes: Only accept derived of Node!"); // 按照localZOrder排序 std::stable_sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) { return n1->_localZOrder < n2->_localZOrder; }); }
6. 在visit排序遍历中,会依次根据localZOrder依次执行draw, 我们以Sprite为例:
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { // 判定纹理是否有效 if (_texture == nullptr) { return; } #if CC_USE_CULLING // Don't calculate the culling if the transform was not updated auto visitingCamera = Camera::getVisitingCamera(); auto defaultCamera = Camera::getDefaultCamera(); if (visitingCamera == defaultCamera) { _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; } else { // XXX: this always return true since _insideBounds = renderer->checkVisibility(transform, _contentSize); } // 判定渲染的纹理是否在可见区域内 if(_insideBounds) #endif { _trianglesCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags); // 将绘制命令添加到renderer绘制栈RenderQueue中 renderer->addCommand(&_trianglesCommand); } }
7. draw并未执行绘制,而是生成了RenderCommand绘制命令,放置到了RenderQueue中。
void Renderer::addCommand(RenderCommand* command) { int renderQueue =_commandGroupStack.top(); addCommand(command, renderQueue); }
8. UI树遍历结束后,会生成一系列的绘制命令,此时Renderer::render()开始对绘制命令进行排序,绘制。
这样做的目地主要使得渲染系统可以对绘制做一些优化,比如:使用相同纹理的Command可执行自动批绘制。
void Renderer::render() { _isRendering = true; if (_glViewAssigned) { for (auto &renderqueue : _renderGroups) { // 执行遍历,遍历的是globalZOrder相关 renderqueue.sort(); } visitRenderQueue(_renderGroups[0]); } clean(); _isRendering = false; }
关于其遍历sort的实现看下:
void RenderQueue::sort() { // 3D相关 std::sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]), std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand); // GLOBALZ_NEG 表示globalZOrder < 0 std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand); // GLOBALZ_POS 表示globalZOrder > 0 std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand); } static bool compareRenderCommand(RenderCommand* a, RenderCommand* b) { return a->getGlobalOrder() < b->getGlobalOrder(); }
在这里,我们可以明白,RenderQueue对RenderCommand下globalZOrder不为0的,又执行了排序。我们可以总结到:
1. 过多的使用globalZOrder会影响UI元素的绘制性能
2. UI元素的绘制顺序并非仅受localZOrder的影响,也会受到globalZOrder的影响
9. 通过 visitRenderQueue 这一步会进入到渲染流程,而最终执行到的代码为:processRenderCommand
void Renderer::processRenderCommand(RenderCommand* command) { auto commandType = command->getType(); if( RenderCommand::Type::TRIANGLES_COMMAND == commandType) { // ... } else if (RenderCommand::Type::MESH_COMMAND == commandType) { flush2D(); // ... } else if(RenderCommand::Type::GROUP_COMMAND == commandType) { flush(); int renderQueueID = ((GroupCommand*) command)->getRenderQueueID(); CCGL_DEBUG_POP_GROUP_MARKER(); } 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::PRIMITIVE_COMMAND == commandType) { flush(); auto cmd = static_cast<PrimitiveCommand*>(command); cmd->execute(); } }
可以看到,更具渲染类型不同,会调用不同的渲染方法等。其相关命令主要有:
enum class Type { /** Reserved type.*/ UNKNOWN_COMMAND, // 用于绘制1个或多个矩形区域,比如Sprite, ParticleSystem QUAD_COMMAND, /**Custom command, used for calling callback for rendering.*/ CUSTOM_COMMAND, /* 用来绘制一个TextureAtlas, 比如Label, TileMap等 而对于TextureAtlas来说,它主要用于对同一纹理下多个精灵的封装 */ BATCH_COMMAND, // 用来包装多个RenderCommand,而其中的RenderCommand不会参与全局排序,多用于ClippingNode,RenderTexture等 GROUP_COMMAND, /**Mesh command, used to draw 3D meshes.*/ MESH_COMMAND, /**Primitive command, used to draw primitives such as lines, points and triangles.*/ PRIMITIVE_COMMAND, /**Triangles command, used to draw triangles.*/ TRIANGLES_COMMAND };
...