从本篇文章开始,将分析Cocos2d-x 3.0源代码,第一部分是从Cocos2d-x学习OpenGL,也就是分析Cocos2d-x 3.0的渲染代码,本篇首先介绍Cocos2d-x 3.0的渲染结构,使用的是3.0正式版。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
//只有一种情况会调用到这里来,就是导演类调用end函数
_purgeDirectorInNextLoop = false ;
//清除导演类
purgeDirector();
}
else if (! _invalid)
{
//绘制
drawScene();
//清除内存
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
|
分析的起点是mainLoop函数,这是在主线程里面会调用的循环,其中drawScene函数进行绘制。那么就进一步来看drawScene函数。
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
71
72
|
void Director::drawScene()
{
//计算间隔时间
calculateDeltaTime();
//如果间隔时间过小会被忽略
if (_deltaTime < FLT_EPSILON)
{
return ;
}
//空函数,也许之后会有作用
if (_openGLView)
{
_openGLView->pollInputEvents();
}
//非暂停状态
if (! _paused)
{
_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)
{
_runningScene->visit(_renderer, identity, false );
_eventDispatcher->dispatchEvent(_eventAfterVisit);
}
//绘制观察节点,如果你需要在场景中设立观察节点,请调用摄像机的setNotificationNode函数
if (_notificationNode)
{
_notificationNode->visit(_renderer, identity, false );
}
//绘制屏幕左下角的状态
if (_displayStats)
{
showStats();
}
//渲染
_renderer->render();
//渲染后
_eventDispatcher->dispatchEvent(_eventAfterDraw);
kmGLPopMatrix();
_totalFrames++;
if (_openGLView)
{
_openGLView->swapBuffers();
}
//计算绘制时间
if (_displayStats)
{
calculateMPF();
}
}
|
其中和绘制相关的是visit的调用和render的调用,其中visit函数会调用节点的draw函数,在3.0之前的版本中draw函数就会直接调用绘制代码,3.0版本是在draw函数中将绘制命令存入到renderer中,然后renderer函数去进行真正的绘制,首先来看sprite的draw函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
//检查是否超出边界,自动裁剪
_insideBounds = 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(&_customDebugDrawCommand);
#endif
}
}
|
这里面用了两种不同的绘制命令quadCommand初始化后就可以加入到绘制命令中,customDebugDrawCommand传入了一个回调函数,具体的命令种类会在后面介绍。其中自定义的customDebugDrawCommand命令在初始化的时候只传入了全局z轴坐标,因为它的绘制函数全部都在传入的回调函数里面,_quadCommand则需要传入全局z轴坐标,贴图名称,shader,混合,坐标点集合,坐标点集个数,变换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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。
这种类型的绘制命令不会在处理命令的时候调用任何一句openGL代码,而是调用你写好并设置给func的绘制函数,后续文章会介绍引擎中的所有自定义绘制,并自己实现一个自定义的绘制。
整个GROUP_COMMAND的原理需要从addCommand讲起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void Renderer::addCommand(RenderCommand* command)
{
//获得栈顶的索引
int renderQueue =_commandGroupStack.top();
//调用真正的addCommand
addCommand(command, renderQueue);
}
void Renderer::addCommand(RenderCommand* command, int renderQueue)
{
CCASSERT(!_isRendering, "Cannot add command while rendering" );
CCASSERT(renderQueue >=0, "Invalid render queue" );
CCASSERT(command->getType() != RenderCommand::Type::UNKNOWN_COMMAND, "Invalid Command Type" );
//将命令加入到数组中
_renderGroups[renderQueue].push_back(command);
}
|
addCommand有“真假”两个,几乎所有添加渲染命令的地方,调用的都是第一个“假” 。
addCommand,它实际上不是真正的把命令添加到_renderGroups中,它是获得需要把命令加入到_renderGroups位置中的索引,这个索引是从_commandGroupStack获得的,_commandGroupStack是个栈,当我们创建一个GROUP_COMMAND时,需要调用pushGroup函数,它是把当前这个命令在_renderGroups的索引位置压到栈顶,当addCommand时,调用top,获得这个位置:
1
2
3
|
_groupCommand.init(_globalZOrder);
renderer->addCommand(&_groupCommand);
renderer->pushGroup(_groupCommand.getRenderQueueID());
|
GROUP_COMMAND一般用于绘制的节点有一个以上的绘制命令,把这些命令组织在一起,无需排定它们之间的顺序,他们作为一个整体被调用,所以一定要记住,栈是push,pop对应的,关于这个节点的所有的绘制命令被添加完成后,请调用pop,将这个值从栈顶弹出,否则后面的命令也会被添加到这里。
它们会在处理GROUP_COMMAND被调用:
1
2
3
4
5
|
else if (RenderCommand::Type::GROUP_COMMAND == commandType) {
flush();
int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
visitRenderQueue(_renderGroups[renderQueueID]);
}
|
如有错误,欢迎指出