Cocos2d-x 3.x的渲染流程

Cocos2d-x v3.0 渲染流水线 路线图

zeroyang

OpenGL ES渲染之Shader准备: http://cn.cocos2d-x.org/tutorial/show?id=1783




为什么(愿景)

现在Cocos2d渲染机制不错,但慢慢开始显得有点跟不上潮流了。而且,它对于现代平板和手机上流行的多核CPU,不能很好地利用它们的优势。


所以我们想要设计Cocos渲染器,使它性能更好、更优雅、更具有扩展性,更灵活,但保持易用性。我们也想要保留Cocos2d原有或是和原有类似的API,那么使用Cocos2d的开发者会感到很舒服,不需要因为底层的改动而烦恼。


我们实现这个渲染器后,依然保留Cocos2d开发者熟悉的、原有的核心的概念,如:场景(Scenes)、节点(Nodes)、图层(Layers)、精灵(Sprites)。


是什么(目标)

以下是一个在Cocos2d v3.0我们将会实现的关于新特性和提高的高层抽象的视图:

  • 从渲染器中把场景图形解耦出来。

访问节点时发布图形命令,并把它们添加到一个队列上,但实际上不调用任何OpenGL的渲染代码。

  • 分形几何选择裁剪视图

精灵(Sprite)以及一般的几何图形,如果它们不出现在摄影机的视图范围内,会自动被当前帧移除,不会被选人呢

  • 渲染线程 所有渲染执行的执行(如:OpenGL调用)会被移到别的线程中执行,而不是在主线程中执行(这将有助于并行处理,更好的利用多核CPU的能力)

  • 自动化批处理

有效地较少绘图调用次数,自动将绘图调用尽可能地成批处理(如:使用同一纹理的精灵)。

  • 基于节点的自定义渲染

在当前的Cocos版本中,开发者能够按需在每个节点的基础上自定义渲染,直接调用OpenGL命令,不顾官方的渲染器(不过很可能导致性能减弱)。

  • 对2D渲染进行优化,同样适用于3D

新的渲染器会对2D游戏进行优化,但它对3D的对象也同样有效。


怎样做(计划)

新设计的核心概念是RenderQueue。当访问一个节点时,渲染器不会再直接调用OpenGL命令了。它会将渲染指令(RenderCommands)添加到RenderQueue中去。在这个队列的命令会被渲染器后台不断地读取、按需处理,并推送到实际上执行渲染的API(比如:OpenGL)(如图)。

渲染器后台(运行在独立线程中),会基于一个键来对命令进行排序,将队列中排序好的命令移除队列,进行处理并实际执行。任何有锁或是消耗大量CPU的OpenGL指令会被渲染器后台线程执行,从而使得Cocos的主线程保持空闲,能继续解析场景图形或者处理和渲染无关的任务。这会对并行处理有帮助,特别是在多核CPU的时候。(如图)

“推送指令”这个步骤会在cocos2d的主线程处理,而"对指令排序"和“执行指令”会在渲染队列(RenderQueue)中处理。


指令

一条指令能够是任何东西,从“画一个四边形“到“执行这段OpenGL”。

每一条指令会有一个原料(material)。原料是以下的组合:

  • 纹理id

  • 着色器程序id

  • 其他混合函数、状态也能被添加

另外地,每一条指令有一个64位的键,用于对指令进行排序。拥有更小的键值的指令能够更快被执行。


生成键

键由以下元素构成:

  • 视图尺寸:3bits

  • 透明/半透明:1 bit

  • 命令(cmd) + 指令ID :1bit + 32bits

  • 深度:24bits

  • 原料ID:24bits

如果半透明位是关闭的(意味着它是一个不透明的对象),那么,深度属性则作废,因为不透明的对象需要被从前到后地绘画,而半透明对象则是从后到前地绘画。


四边形指令

RenderCommandQuads是一个预定义的指令,它会使用一个或多个四边形遍历缓冲区(VBO)。这条指令将被精灵(每个精灵对应1个四边形)、TileMap(对应多个四边形)、SpriteBatchNode(对应多个四边形)、DrawNode(对应多个四边形)使用。


OpenGL指令

通过创建指令来执行自定义的OpenGL调用是可行的(就像在Cocos2d-x v2.x一样)。

为了完成这个任务,开发者需要创建一个RenderCommandOpenGL对象,然后通过OpenGL调用设置xxx属性为一个lambda 对象。

开发者有必要在RenderCommandOpenGL执行后恢复渲染队列的状态。

尽管我们能方便地执行RenderCommandOpenGL的指令,在上下文切换中依然可能出现比较大的性能消耗。另外它也会冲刷四边形的缓冲区。(详见自动批处理)。


3D指令

尽管渲染器为了2D游戏作了优化,但同时也支持3D对象。

为了渲染3D的对象,开发者必须使用一个 Command的子类并对其需要的特性进行实现。


组指令

RenderCommandGroup是一个特殊的指令,它会让渲染器:

  • 下列的指令必须被分在同一组。

  • 它们不与其他“全局”指令作排序。取而代之的是,它们之间作排序。

用途:

  • ClippingNode:

        它的子代必须按照Clipping规则被绘画。

  • RenderTexture

        它的自带必须在一个新纹理上被绘画

        其他需要对自己的子代作出影响的节点


自动批处理

在Cocos2d-x 3.0里我们想要介绍自动批处理的概念。实际上,我们相信通过减少绘图调用的次数和渲染设备的状态改变会提高动态渲染的速度。

另外,RenderQueue会使用一个缓冲区来绘制四边形(Sprite,Tiles,Drawing primitives)。对四边形的唯一要求是,四边形的每个顶点必须有3个这样的属性:

  • Vertex:3 floats(x,y,z)

  • 纹理坐标(Texture coordinates):2 floats(u,v)

  • 颜色:4bytes(r,g,b,a)

这些四边形不需要共享相同的原料。但如果所有的四边形共享相同的原料,那么只需要一个绘图调用(自动批处理)。

一个简化版的自动批处理算法工作流程如下:

  1. 把一个新的四边形添加到缓冲区。

  2. 如果新添加的四边形和以前的四边形共享相同的原料,没有事情发生。

  3. 然而,如果新四边形有新的原料,则绘制旧的四边形。

只使用一个缓冲区(VBO)从而最小化缓冲区切换。

缓冲区会有一个最大的容量——10922个四边形(65536/4)如果缓冲区满了,那么缓冲区会被冲刷(所有没有绘制的四边形会被绘制),然后它能够被再次被使用。


自动裁剪

自动裁剪会在cocos2d主线程被执行。会对每一个节点进行检查。如果节点的AABB在屏幕之外,那么没有指令会被提交到RenderQueue中。


实现细节

节点

ClippingNode

可能的实现:

它会重写visit函数,并发送一条“Push Group;Push Clipping Area;Set Clippiing Area”的指令。

在离开visit函数前,它必须执行“Pop Clipping Area;PopGroup”的指令。

渲染器必须有创建Clipping Area的功能。

DrawNode

需要更深的研究,但RenderCommandQuads似乎是可行的。

它很可能需要自己的着色器,与别的节点一起处理似乎不可行。

Label

它必须使用RenderCommandQuads。

默认的着色器足够好了,但如果距离属性被实现了,它可能需要使用自己的着色器,所以和别的节点一起批处理可能是不可行的。

Layer

它是一个空的、无效的节点,没有指令会被发送到队列中去。

LayerColor、LayerGradient

这些节点应该被DrawNode取代。

如果它们没有被取代,它们必须使用RenderCommandQuads。

Menu

它是一个空的、无效的节点。没有指令会被发送到队列中去。

MenuItem

MenuItemImage、MenuItemSprite、MenuItemLabel必须使用RenderCommandQuads。

MotionStreak

有待讨论

Node

它是一个空的、无效的节点。没有指令会被发送到队列中去。

ParallaxNode

它是一个空的、无效的节点。没有指令会被发送到队列中去。

ParticleSystem

ParticleSystem使用一个有V3F_C4B_T2F四边形的数组来渲染粒子。所以,一个RenderCommandQuads足够了,让批处理精灵和粒子变得有可行性。

ParticleSystemBatchNode

和ParticleSystem一样,不过移除这个节点大部分时候是安全的。

ProgressTimer

有待讨论

RenderTexture

可能的实现:

它必须重写visit函数,发送"Push Group Command;Push Texture;Switch To Texture"的指令。

在离开visit函数前,它必须执行“Pop Texture;Pop Group”的指令。

渲染西必须有创建新Render Texture对象的功能。

Scene

它是一个空的、无效的节点。没有指令会被发送到队列中去。

Sprite

它只给RenderCommandQuads发送一个四边形。

SpriteBatchNode

它发送一条RenderCommandQuads指令以及许多包含在批处理中得精灵的四边形。

不包含在内的精灵在渲染器中不进行排序是有价值的。育儿呆滞的是,RenderCommandQuads会基于SpriteBatchNode的值被排序,而不是SpriteBatchNode的子代值。

TextField

TMXTiledMap

和SpriteBatchNode一样

渲染器伪代码

https://gist.github.com/ricardoquesada/7049216


引用

  • 把你的绘图速度提高两倍

  • 把你的绘图调用排序

  • 多平台的灵活渲染(GDC2012)

  • Doom3 BFG 源代码审查

  • GPU PRO3:先进的渲染技巧:数据驱动式渲染器

  • 实现一个游戏的渲染队列

  • 重构引擎:渲染子系统

  • 多线程渲染循环

  • 渲染状态改变的开销

  • 输入等待时间

  • 自己动手完成游戏任务调度

  • 学习喜欢你的Z-buffer

  • Qt快速场景图形渲染器

  • UITableViewCell Class Reference


Misc

  • Gingko游戏循环

  • Gingko介绍

  • 不可思议的Fast C++ delegates

  • 高性能游戏一个动态组件架构

  • 有关gIDepthMask用法的问题





Cocos2d-x 3.2与OpenGL渲染总结(一):Cocos2d-x 3.2的渲染流程


最近几天,我都在学习如何在Cocos2d-x 3.2中使用OpenGL来实现对图形的渲染。在网上也看到了很多好的文章,在这些文章基础上做了这次的我个人认为比较完整的总结。当你了解了Cocos2d-x 3.2中对图形渲染的流程,你就会觉得要学会写自己的shader才是最重要的。


第一、渲染流程从2.x到3.x的变化

在2.x中,渲染过程是通过递归渲染树(Rendering tree)这种图关系来渲染关系图。递归调用visit()函数,并且在visit()函数中调用该节点的draw函数渲染各个节点,此时draw函数的作用是直接调用OpenGL代码进行图形的渲染。由于visit()和draw函数都是虚函数,所以要注意执行时的多态。那么我们来看看2.x版本中CCSprite的draw函数,如代码1。

代码1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//这是cocos2d-2.0-x-2.0.4版本的CCSprite的draw函数
void  CCSprite::draw( void )
{
     CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite,  "CCSprite - draw" );
     CCAssert(!m_pobBatchNode,  "If CCSprite is being rendered by CCSpriteBatchNode, CCSprite#draw SHOULD NOT be called" );
     CC_NODE_DRAW_SETUP();
     ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );
     if  (m_pobTexture != NULL)
     {
         ccGLBindTexture2D( m_pobTexture->getName() );
     }
     else
     {
         ccGLBindTexture2D(0);
     }  
     //
     // Attributes
     //
     ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );
#define kQuadSize sizeof(m_sQuad.bl)
     long  offset = ( long )&m_sQuad;
     // vertex
     int  diff = offsetof( ccV3F_C4B_T2F, vertices);
     glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, ( void *) (offset + diff));
     // texCoods
     diff = offsetof( ccV3F_C4B_T2F, texCoords);
     glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, ( void *)(offset + diff));
     // color
     diff = offsetof( ccV3F_C4B_T2F, colors);
     glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, ( void *)(offset + diff));
     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
     CHECK_GL_ERROR_DEBUG();
#if CC_SPRITE_DEBUG_DRAW == 1
     // draw bounding box
     CCPoint vertices[4]={
         ccp(m_sQuad.tl.vertices.x,m_sQuad.tl.vertices.y),
         ccp(m_sQuad.bl.vertices.x,m_sQuad.bl.vertices.y),
         ccp(m_sQuad.br.vertices.x,m_sQuad.br.vertices.y),
         ccp(m_sQuad.tr.vertices.x,m_sQuad.tr.vertices.y),
     };
     ccDrawPoly(vertices, 4,  true );
#elif CC_SPRITE_DEBUG_DRAW == 2
     // draw texture box
     CCSize s =  this ->getTextureRect().size;
     CCPoint offsetPix =  this ->getOffsetPosition();
     CCPoint vertices[4] = {
         ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
         ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+s.height)
     };
     ccDrawPoly(vertices, 4,  true );
#endif // CC_SPRITE_DEBUG_DRAW
 
     CC_INCREMENT_GL_DRAWS(1);
 
     CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite,  "CCSprite - draw" );
}


那么我们也看看3.x中Sprite的draw函数,如代码2。

代码2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void  Sprite::draw(Renderer *renderer,  const  Mat4 &transform, uint32_t flags)
{
     // Don't do calculate the culling if the transform was not updated
     _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
     if (_insideBounds)
     {
         _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _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(&_customDebugDrawCommand);
#endif //CC_SPRITE_DEBUG_DRAW
     }
}

从代码1和代码2的对比中,我们很容易就发现2.x版本中的draw函数直接调用OpengGL代码进行图形渲染,而3.x版本中draw的作用是把RenderCommand添加到CommandQueue中,至于这样做的好处是,实际的渲染API进入其中一个与显卡直接交流的有独立线程的RenderQueue。


从Cocos2d-x 3.0开始,Cocos2d-x引入了新的渲染流程,它不像2.x版本直接在每一个node中的draw函数中直接调用OpenGL代码进行图形渲染,而是通过各种RenderCommand封装起来,然后添加到一个CommandQueue队列里面去,而现在draw函数的作用就是在此函数中设置好相对应的RenderCommand参数,然后把此RenderCommand添加到CommandQueue中。最后在每一帧结束时调用renderer函数进行渲染,在renderer函数中会根据ID对RenderCommand进行排序,然后才进行渲染。


下面我们来看看图1、图2,这两个图形象地表现了Cocos2d-x3.x下RenderCommand的封装与传递与及RenderCommand的排序。

图1:

图2:

上面所说的各个方面都有点零碎,下面就对渲染的整个流程来一个从头到尾的梳理。下面是针对3.2版本的,对于2.x版本的梳理不做梳理,因为我用的是3.2版本。


首先,我们Cocos2d-x的执行是通过Application::run()来开始的,如代码3,此代码目录中在xx\cocos2d\cocos\platform\对应平台的目录下,这是与多平台实现有关的类,关于如何实现多平台的编译,你可以参考《Cocos2d-x3.2源码分析(一)类FileUtils--实现把资源放在Resources文件目录下达到多平台的引用》中我对平台编译的分析。以防篇幅过长,只截取了重要部分,如需详解,可以直接查看源码。


代码3:

1
2
3
4
5
6
int  Application::run()
{
   ...
   director->mainLoop();
   ...
}

从代码3中,它明显的启发着我们要继续追寻Director::mainLoop()函数。在Director中mainLoop()为纯函数,此子类DisplayLinkDirector才有其实现,如代码4。

代码4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void  DisplayLinkDirector::mainLoop()
{   
     class = "comment" > //只有一种情况会调用到这里来,就是导演类调用end函数  
     if  (_purgeDirectorInNextLoop)
     {
         _purgeDirectorInNextLoop =  false ;
         class = "comment" > //清除导演类
         purgeDirector();
     }
     else  if  (! _invalid)
     {    class = "comment" > //绘制 
         drawScene(); 
         //清除当前内存池中对象,即池中每一个对象--_referenceCount
         PoolManager::getInstance()->getCurrentPool()->clear();
     }
}

mainLoop是主线程调用的循环,其中drawScene()是绘制函数,接着我们继续追寻它的代码,如代码5。

代码5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void  Director::drawScene()
{
     class = "comment" > //计算间隔时间 
     calculateDeltaTime();
     
     //忽略该帧如果时间间隔接近0
     if (_deltaTime < FLT_EPSILON)
     {
         return ;
     }
 
     if  (_openGLView)
     {
         _openGLView->pollInputEvents();
     }
 
     //tick before glClear: issue #533
     if  (! _paused)
     {
         _scheduler->update(_deltaTime);
         _eventDispatcher->dispatchEvent(_eventAfterUpdate);
     }
 
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
     /* to avoid flickr, nextScene MUST be here: after tick and before draw.
      XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
     if  (_nextScene)
     {
         setNextScene();
     }
 
     pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
 
     // draw the scene
     if  (_runningScene)
     {
         _runningScene->visit(_renderer, Mat4::IDENTITY,  false );
         _eventDispatcher->dispatchEvent(_eventAfterVisit);
     }
 
     // draw the notifications node
     if  (_notificationNode)
     {
         _notificationNode->visit(_renderer, Mat4::IDENTITY,  false );
     }
 
     if  (_displayStats)
     {
         showStats();
     }
 
     _renderer->render();
     _eventDispatcher->dispatchEvent(_eventAfterDraw);
 
     popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
 
     _totalFrames++;
 
     // swap buffers
     if  (_openGLView)
     {
         _openGLView->swapBuffers();
     }
 
     if  (_displayStats)
     {
         calculateMPF();
     }
}

从代码5中,我们看见visit()和render()函数的调用。其中visit()函数会调用draw()函数来向RenderQueue中添加RenderCommand,那么就继续追寻visit()的代码,如代码6。

代码6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void  Node::visit(Renderer* renderer,  const  Mat4 &parentTransform, uint32_t parentFlags)
{
     // quick return if not visible. children won't be drawn.
     if  (!_visible)
     {
         return ;
     }
 
     uint32_t flags = processParentFlags(parentTransform, parentFlags);
 
     // IMPORTANT:
     // To ease the migration to v3.0, we still support the Mat4 stack,
     // but it is deprecated and your code should not rely on it
     Director* director = Director::getInstance();
     director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
     director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);
     int  i = 0;
     if (!_children.empty())
     {
         sortAllChildren();
         // draw children zOrder < 0
         for ( ; i < _children.size(); i++ )
         {
             auto node = _children.at(i);
 
             if  ( node && node->_localZOrder < 0 )
                 node->visit(renderer, _modelViewTransform, flags);
             else
                 break ;
         }
         // self draw
         this ->draw(renderer, _modelViewTransform, flags);
 
         for (auto it=_children.cbegin()+i; it != _children.cend(); ++it)
             (*it)->visit(renderer, _modelViewTransform, flags);
     }
     else
     {
         this ->draw(renderer, _modelViewTransform, flags);
     }
 
     director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
     
     // FIX ME: Why need to set _orderOfArrival to 0??
     // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
     // reset for next frame
     // _orderOfArrival = 0;
}

从代码6中,我们可以看到“ auto node = _children.at(i);和node->visit(renderer, _modelViewTransform, flags);”,这段代码的意思是先获取子节点,然后递归调用节点的visit()函数,到了没有子节点的节点,开始调用draw()函数。那么我们看看draw()函数代码,如代码7。

代码7:

1
2
3
void  Node::draw(Renderer* renderer,  const  Mat4 &transform, uint32_t flags)
{
}

好吧,从代码7中,我们看到Node的draw什么都没有做,是我们找错地方?原来draw()是虚函数,所以它执行时执行的是该字节类的draw()函数。确实是我们找错地方了。那么我们分别看DrawNode::draw()、Sprite::draw()。

代码8:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void  DrawNode::draw(Renderer *renderer,  const  Mat4 &transform, uint32_t flags)
{
     _customCommand.init(_globalZOrder);
     _customCommand.func = CC_CALLBACK_0(DrawNode::onDraw,  this , transform, flags);
     renderer->addCommand(&_customCommand);
}
 
void  Sprite::draw(Renderer *renderer,  const  Mat4 &transform, uint32_t flags)
{
     // Don't do calculate the culling if the transform was not updated
     _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
 
     if (_insideBounds)
     {
         _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _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(&_customDebugDrawCommand);
         #endif  //CC_SPRITE_DEBUG_DRAW
     }
}

从代码8中,我们可以看到在draw()函数向RenderQueue中添加RenderCommand,当然有的类的draw()不是向RenderQueue中添加RenderCommand,而是直接使用OpenGL的API直接进行渲染,或者做一些其他的事情。


那么当draw()都递归调用完了,我们来看看最后进行渲染的Renderer::render() 函数,如代码9。

代码9:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void  Renderer::render()
{
     //Uncomment this once everything is rendered by new renderer
      //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
     //TODO setup camera or MVP
     _isRendering =  true ;
     
     if  (_glViewAssigned)
     {
         // cleanup
         _drawnBatches = _drawnVertices = 0;
 
         //Process render commands
         //1. Sort render commands based on ID
         for  (auto &renderqueue : _renderGroups)
         {
             renderqueue.sort();
         }
         visitRenderQueue(_renderGroups[0]);
         flush();
     }
     clean();
     _isRendering =  false ;
}

从代码9中,我们看到“renderqueue.sort()",这是之前所说的对命令先排序,然后才进行渲染,“visitRenderQueue( _renderGroups[0])”就是来进行渲染的。那么我们接着看看void Renderer::visitRenderQueue(const RenderQueue& queue)的代码,如代码10。

代码10:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void  Renderer::visitRenderQueue( const  RenderQueue& queue)
{
     ssize_t size = queue.size();
     
     for  (ssize_t index = 0; index < size; ++index)
     {
         auto command = queue[index];
         auto commandType = command->getType();
         if (RenderCommand::Type::QUAD_COMMAND == commandType)
         {
             flush3D();
             auto cmd =  static_cast (command);
             //Batch quads
             if (_numQuads + cmd->getQuadCount() > VBO_SIZE)
             {
                 CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() < VBO_SIZE,  "VBO is not big enough for quad data, please break the quad data down or use customized render command" );
                 
                 //Draw batched quads if VBO is full
                 drawBatchedQuads();
             }
             
             _batchedQuadCommands.push_back(cmd);
             
             memcpy (_quads + _numQuads, cmd->getQuads(),  sizeof (V3F_C4B_T2F_Quad) * cmd->getQuadCount());
             convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView());
             
             _numQuads += cmd->getQuadCount();
 
         }
         else  if (RenderCommand::Type::GROUP_COMMAND == commandType)
         {
             flush();
             int  renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
             visitRenderQueue(_renderGroups[renderQueueID]);
         }
         else  if (RenderCommand::Type::CUSTOM_COMMAND == commandType)
         {
             flush();
             auto cmd =  static_cast (command);
             cmd->execute();
         }
         else  if (RenderCommand::Type::BATCH_COMMAND == commandType)
         {
             flush();
             auto cmd =  static_cast (command);
             cmd->execute();
         }
         else  if  (RenderCommand::Type::MESH_COMMAND == commandType)
         {
             flush2D();
             auto cmd =  static_cast (command);
             if  (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())
             {
                 flush3D();
                 cmd->preBatchDraw();
                 cmd->batchDraw();
                 _lastBatchedMeshCommand = cmd;
             }
             else
             {
                 cmd->batchDraw();
             }
         }
         else
         {
             CCLOGERROR( "Unknown commands in renderQueue" );
         }
     }
}

从代码10中,我们看到RenderCommand类型有QUAD_COMMAND,CUSTOM_COMMAND,BATCH_COMMAND,GROUP_COMMAND,MESH_COMMAND五种,这些类型的讲解在下一节。

从代码10中,好像没有与OpenGL相关的代码,有点囧。其实这OpenGL的API调用是在Renderer::drawBatchedQuads()、BatchCommand::execute()中。在代码10中,我们也看到在QUAD_COMMAND类型中调用了drawBatchedQuads(),如代码11。在CUSTOM_COMMAND中调用了CustomCommand::execute(),如代码12。在BATCH_COMMAND中调用了BatchCommand::execute(),如代码13。在MESH_COMMAND类型中调用了MeshCommand::preBatchDraw()和MeshCommand::batchDraw()。至于GROUP_COMMAND类型,就递归它组里的成员。

代码11:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

你可能感兴趣的:(cocos,openGL)