1. 程式人生 > >bytemode stay hungry stay foolish cocos2dx渲染架構

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]);
  
  }