Cocos2d-x绘制系统

旧的绘制系统

每个元素的绘制逻辑均分布于每个元素内部的draw方法中,并且紧密依赖于UI树的遍历。不易拓展优化。eg:依赖UI树的遍历顺序导致无法在多个层级之间调整绘制顺序,各个绘制逻辑分布在每个元素内部不利于针对绘制进行优化(如自动批绘制)

新的绘制系统

新的绘制系统将绘制部分从UI树的遍历中分离出来

特点

  1. 每个UI元素的类型更多的是根据它在应用程序中的特征而不是绘制方式不同划分的(多个不同类型的UI元素可能拥有相同的绘制方式)之前的每个UI元素拥有自己的绘制逻辑
  2. 采用应用程序级别的视口裁剪。即一个UI元素在场景中的坐标位于视窗区域之外时,就不会发送绘制命令到绘制栈上,避免了OpenGL在图元装配阶段的工作
  3. 采用自动批绘制技术:如果在一个场景中有很多的元素都使用同一张纹理,同一个着色器程序,理论上可以只调用一次绘制命令。自动批绘制需要相关的绘制命令在执行顺序上相邻。可以减少OpenGL的Draw Calls
  4. 更简单的实现绘制的自定义

流程

生成绘制命令

通过UI树的遍历,给每个元素生成一个绘制命令。

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
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 == nullptr) {
_insideBounds = true;
}
else 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->addCommand(&_trianglesCommand);

#if CC_SPRITE_DEBUG_DRAW
_debugDrawNode->clear();
auto count = _polyInfo.triangles.indexCount/3;
auto indices = _polyInfo.triangles.indices;
auto verts = _polyInfo.triangles.verts;
for(ssize_t i = 0; i < count; i++)
{
//draw 3 lines
Vec3 from =verts[indices[i*3]].vertices;
Vec3 to = verts[indices[i*3+1]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);

from =verts[indices[i*3+1]].vertices;
to = verts[indices[i*3+2]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);

from =verts[indices[i*3+2]].vertices;
to = verts[indices[i*3]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
}
#endif //CC_SPRITE_DEBUG_DRAW
}
}

RenderCommand表示一个绘制类型,定义了怎样绘制一个UI元素。一般情况下每个UI元素会关联0个过着一个RenderCommand并在draw方法中将会致命了发送给renderer。

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
class CC_DLL RenderCommand

{

public:

/**Enum the type of render command. */

enum class Type

{

/** Reserved type.*/

UNKNOWN_COMMAND,

/** Quad command, used for draw quad.*/

QUAD_COMMAND,

/**Custom command, used for calling callback for rendering.*/

CUSTOM_COMMAND,

/**Batch command, used for draw batches in texture atlas.*/

BATCH_COMMAND,

/**Group command, which can group command in a tree hierarchy.*/

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

};

GROUP_COMMAND用来包装多个RenderCommand的集合,GroupCommand中的每一个RenderCommand都不会参与全局排序,它可以用来实现子元素裁剪,绘制子元素到纹理

TrianglesCommand继承RenderCommand。class CC_DLL TrianglesCommand : public RenderCommand

http://www.cocos2d-x.org/reference/native-cpp/V3.5/d9/d0d/classcocos2d_1_1_triangles_command.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TrianglesCommand _trianglesCommand; 

_trianglesCommand.init(_globalZOrder,
_texture,
getGLProgramState(),
_blendFunc,
_polyInfo.triangles,
transform,
flags);
renderer->addCommand(&_trianglesCommand);
//float globalOrder, 全局zorder,决定渲染先后顺序
//GLuint textureID, 纹理id,只有一个,没有纹理混合的功能
//GLProgramState* glProgramState, 着色程序及其参数
//BlendFunc blendType, 颜色混合函数
//const Triangles& triangles,三角形顶点数据,包括顶点坐标、颜色、纹理坐标
//const Mat4& mv, 变换矩阵
//uint32_t flags,标志位

只发送绘制命令,将每个元素的绘制部分从UI树的遍历过程中抽取出来统一处理,这样可以灵活的调整不同UI层级之间的元素绘制顺序,和特点3

绘制命令排序

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
RenderQueue 

/**

RenderCommand will be divided into Queue Groups.

*/

enum QUEUE_GROUP

{

/**Objects with globalZ smaller than 0.*/

GLOBALZ_NEG = 0,

/**Opaque 3D objects with 0 globalZ.*/

OPAQUE_3D = 1,

/**Transparent 3D objects with 0 globalZ.*/

TRANSPARENT_3D = 2,

/**2D objects with 0 globalZ.*/

GLOBALZ_ZERO = 3,

/**Objects with globalZ bigger than 0.*/

GLOBALZ_POS = 4,

QUEUE_COUNT = 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
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)
{
//Process render commands
//1. Sort render commands based on ID
for (auto &renderqueue : _renderGroups)
{
renderqueue.sort();
}
visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;
}



void Renderer::visitRenderQueue(RenderQueue& queue)

{

queue.saveRenderState();



//

//Process Global-Z < 0 Objects

//

const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);

if (zNegQueue.size() > 0)

{

if(_isDepthTestFor2D)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(true);

}

else

{

glDisable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(false);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

}

glDisable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setCullFace(false);



for (const auto& zNegNext : zNegQueue)

{

processRenderCommand(zNegNext);

}

flush();

}



//

//Process Opaque Object

//

const auto& opaqueQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::OPAQUE_3D);

if (opaqueQueue.size() > 0)

{

//Clear depth to achieve layered rendering

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glDisable(GL_BLEND);

glEnable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(false);

RenderState::StateBlock::_defaultState->setCullFace(true);

for (const auto& opaqueNext : opaqueQueue)

{

processRenderCommand(opaqueNext);

}

flush();

}



//

//Process 3D Transparent object

//

const auto& transQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::TRANSPARENT_3D);

if (transQueue.size() > 0)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);

glEnable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

RenderState::StateBlock::_defaultState->setCullFace(true);

for (const auto& transNext : transQueue)

{

processRenderCommand(transNext);

}

flush();

}



//

//Process Global-Z = 0 Queue

//

const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO);

if (zZeroQueue.size() > 0)

{

if(_isDepthTestFor2D)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(true);

}

else

{

glDisable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(false);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

}

glDisable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setCullFace(false);



for (const auto& zZeroNext : zZeroQueue)

{

processRenderCommand(zZeroNext);

}

flush();

}



//

//Process Global-Z > 0 Queue

//

const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS);

if (zPosQueue.size() > 0)

{

if(_isDepthTestFor2D)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glEnable(GL_BLEND);



RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(true);

}

else

{

glDisable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);



RenderState::StateBlock::_defaultState->setDepthTest(false);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

}

glDisable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setCullFace(false);



for (const auto& zPosNext : zPosQueue)

{

processRenderCommand(zPosNext);

}

flush();

}



queue.restoreRenderState();

}

在UI全部遍历完成之后,执行命令之前,对栈上的绘制命令进行排序,然后按照新的顺序执行它们

绘制顺序首先由globalZOrder决定,然后才是按照元素的遍历顺序

Render实际上维护着一个RenderQueue的数组,每个RenderQueue对应一组RenderCommand或者一个GroupCommand,这些RenderQueue不是简单的线性关系,而是通过GroupCommand构成的树状的关系

####执行绘制

对于一般的RenderCommand按顺序执行,对于相邻使用相同纹理的QuadCommand,将会组合成一个。也就是自动批绘制

GroupCommand

GroupCommand通常不包括具体的GL绘制命令,它只指向一个RenderQueue。当渲染系统绘制一个GroupCommand时,它将找到对应的RenderQueue,然后执行其中的RenderCommand,

把一组RenderCommand加入GroupCommand指向的RenderQueue中,则可以实现对这组RenderCommand的独立绘制,执行顺序不会受其它RenderCommand的影响