bytemode stay hungry stay foolish cocos2dx渲染架构

  2dx的时代UI树便利和渲染是没有分开的,遍历UI树的时候就渲染.3dx版本为了分离了ui树的遍历和渲染,先遍历生成渲染命令发到渲染队列,之后遍历渲染命令队列开始渲染.这样做的好处是渲染命令可以重用,单独的渲染可以做优化例如自动批绘制.本篇首先介绍cocos2D-X 3.x版本的渲染结构,之后会深入opengl es.
  
  mainLoop
  
  void DisplayLinkDirector::mainLoop()
  
  {
  
  if (_purgeDirectorInNextLoop)
  
  {
  
  //只有一种情况会调用到这里来,就是导演类调用end函数
  
  _purgeDirectorInNextLoop = false;
  
  //清除导演类
  
  purgeDirector();
  
  }
  
  else if (! _invalid)
  
  {
  
  //绘制
  
  drawScene();
  
  //清除内存
  
  PoolManager::getInstance()->getCurrentPool()->clear();
  
  }
  
  }
  
  分析的起点是mainLoop函数,这是在主线程里面会调用的循环,其中drawScene函数进行绘制。那么就进一步来看drawScene函数。mainLoop实在opengl的ondrawframe调用过来的即平台每帧渲染会调用.
  
  drawScene
  
  void Director::drawScene()
  
  {
  
  //计算间隔时间
  
  calculateDeltaTime();
  
  //如果间隔时间过小会被忽略
  
  if(_deltaTime < FLT_EPSILON){ return;}
  
  //空函数,也许之后会有作用
  
  if (_openGLView)
  
  {
  
  _openGLView->pollInputEvents();
  
  }
  
  //非暂停状态
  
  if (! _paused)
  
  {
  
  //scheduler更新 会使actionmanager更新和相关的schedule更新 引擎物理模拟都是在绘制之前做的
  
  _scheduler->update(_deltaTime);
  
  _eventDispatcher->dispatchEvent(_eventAfterUpdate);
  
  }
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  //切换下一场景,必须放在逻辑后绘制前,否则会出bug
  
  if (_nextScene)
  
  {
  
  setNextScene();
  
  }
  
  kmGLPushMatrix();
  
  //创建单位矩阵
  
  kmMat4 identity;
  
  kmMat4Identity(&identity);
  
  //绘制场景
  
  if (_runningScene)
  
  {
  
  //递归的遍历scene中的每个node的visit生成渲染命令放入渲染队列
  
  _runningScene->visit(www.wanmeiyuele.cn_renderer, identity, false);
  
  _eventDispatcher->dispatchEvent(_eventAfterVisit);
  
  }
  
  //绘制观察节点,如果你需要在场景中设立观察节点,请调用摄像机的setNotificationNode函数
  
  if (_notificationNode)
  
  {
  
  _notificationNode->visit(_www.michenggw.com renderer, identity, false);//这是一个常驻节点
  
  }
  
  //绘制屏幕左下角的状态
  
  if (_displayStats)
  
  {
  
  showStats();
  
  }
  
  //渲染
  
  _renderer->render();
  
  //渲染后
  
  _eventDispatcher->dispatchEvent(_eventAfterDraw);
  
  kmGLPopMatrix(www.gcyl152.com;
  
  _totalFrames++;
  
  if (_openGLView)
  
  {
  
  _openGLView->swapBuffers(); //交换缓冲区
  
  }
  
  //计算绘制时间
  
  if (_displayStats)
  
  {
  
  calculateMPF();
  
  }
  
  }
  
  其中和绘制相关的是visit的调用和render的调用,其中visit函数会调用节点的draw函数,在3.x之前的版本中draw函数就会直接调用绘制代码,3.x版本是在draw函数中生成将绘制命令放入到renderer队列中,然后renderer函数去进行真正的绘制,首先来看sprite的draw函数.
  
  渲染命令
  
  void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
  
  {
  
  //检查是否超出边界,自动裁剪
  
  _insideBounds www.tiaotiaoylzc.com/= transformUpdated ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
  
  if(_insideBounds)
  
  {
  
  //初始化
  
  _quadCommand.init(_globalZOrder, _texture->getName(), _shaderProgram, _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(&www.tongqt178.com_customDebugDrawCommand);
  
  endif
  
  }
  
  }
  
  这里面用了两种不同的绘制命令quadCommand初始化后就可以加入到绘制命令中,customDebugDrawCommand传入了一个回调函数,具体的命令种类会在后面介绍。其中自定义的customDebugDrawCommand命令在初始化的时候只传入了全局z轴坐标,因为它的绘制函数全部都在传入的回调函数里面,_quadCommand则需要传入全局z轴坐标,贴图名称,shader,混合,坐标点集合,坐标点集个数,变换。
  
  Render
  
  void Renderer::render()
  
  {
  
  _isRendering = true;
  
  if (_glViewAssigned)
  
  {
  
  //清除
  
  _drawnBatches = _drawnVertices = 0;
  
  //排序
  
  for (auto &renderqueue : _renderGroups)
  
  {
  
  renderqueue.sort();
  
  }
  
  //绘制
  
  visitRenderQueue(_renderGroups[0]);
  
  flush();
  
  }
  
  clean();
  
  _isRendering = false;
  
  }
  
  Render类中的render函数进行真正的绘制,首先排序,再进行绘制,从列表中的第一个组开始绘制。在visitRenderQueue函数中可以看到五种不同类型的绘制命令类型,分别对应五个类,这五个类都继承自RenderCommand。
  
  绘制命令
  
  QUAD_COMMAND:
  
  QuadCommand类绘制精灵等。所有绘制图片的命令都会调用到这里,处理这个类型命令的代码就是绘制贴图的openGL代码,
  
  CUSTOM_COMMAND:
  
  自定义绘制,自己定义绘制函数,在调用绘制时只需调用已经传进来的回调函数就可以,裁剪节点,绘制图形节点都采用这个绘制,把绘制函数定义在自己的类里。这种类型的绘制命令不会在处理命令的时候调用任何一句openGL代码,而是调用你写好并设置给func的绘制函数,并自己实现一个自定义的绘制。
  
  BATCH_COMMAND:
  
  批处理绘制,批处理精灵和粒子,其实它类似于自定义绘制,也不会再render函数中出现任何一句openGL函数,它调用一个固定的函数。
  
  GROUP_COMMAND:
  
  绘制组,一个节点包括两个以上绘制命令的时候,把这个绘制命令存储到另外一个renderGroups中的元素中,并把这个元素的指针作为一个节点存储到renderGroups[0]中。
  
  render流程
  
  void Renderer::addCommand(RenderCommand* command)
  
  {
  
  //获得栈顶的索引
  
  int renderQueue =_commandGroupStack.top();
  
  //调用真正的addCommand
  
  addCommand(command, renderQueue);
  
  }
  
  void Renderer::addCommand(RenderCommand* command, int renderQueue)
  
  {
  
  //将命令加入到数组中
  
  _renderGroups[renderQueue].push_back(command);
  
  }
  
  addCommand它是获得需要把命令加入到renderGroups位置中的索引,这个索引是从commandGroupStack获得的,commandGroupStack是个栈,当我们创建一个GROUP_COMMAND时,需要调用pushGroup函数,它是把当前这个命令在_renderGroups的索引位置压到栈顶,当addCommand时,调用top,获得这个位置
  
  groupCommand.init(globalZOrder);
  
  renderer->addCommand(&_groupCommand);
  
  renderer->pushGroup(_groupCommand.getRenderQueueID());
  
  GROUP_COMMAND一般用于绘制的节点有一个以上的绘制命 令,把这些命令组织在一起,无需排定它们之间的顺序,他们作为一个整体被调用,所以一定要记住,栈是push,pop对应的,关于这个节点的所有的绘制命令被添加完成后,请调用pop,将这个值从栈顶弹出,否则后面的命令也会被添加到这里。
  
  为什么调用的起始只需调用为什么只是0,其他的呢?
  
  visitRenderQueue(_renderGroups[0]);
  
  它们会在处理GROUP_COMMAND被调用
  
  else if(RenderCommand::Type::GROUP_COMMAND == commandType) {
  
  flush();
  
  int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
  
  visitRenderQueue(_renderGroups[renderQueueID]);
  
  }

猜你喜欢

转载自blog.csdn.net/li123128/article/details/85057858
今日推荐