cocos2dx3.x中,Sprite调用draw函数时会添加一个TrianglesCommand类型的渲染指令。
CCSprite.cpp
_trianglesCommand.init(_globalZOrder,_texture,getGLProgramState(),_blendFunc,_polyInfo.triangles,transform,flags);
renderer->addCommand(&_trianglesCommand);
在render执行这个指令时,TrianglesCommand并不会立即执行opengl的渲染,而是把它添加到一个std::vector
CCRender.cpp
if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
{
// flush other queues
flush3D();
auto cmd = static_cast<TrianglesCommand*>(command);
// flush own queue when buffer is full
if(_filledVertex + cmd->getVertexCount() > VBO_SIZE || _filledIndex + cmd->getIndexCount() > INDEX_VBO_SIZE)
{
CCASSERT(cmd->getVertexCount()>= 0 && cmd->getVertexCount() < VBO_SIZE,"VBO for vertex is not big enough,please break the data down or use customized render command");
CCASSERT(cmd->getIndexCount()>= 0 && cmd->getIndexCount() < INDEX_VBO_SIZE,"VBO for index is not big enough,please break the data down or use customized render command");
drawBatchedTriangles();
}
// queue it
_queuedTriangleCommands.push_back(cmd);
_filledIndex += cmd->getIndexCount();
_filledVertex += cmd->getVertexCount();
}
这里做了一个限制,如果当前顶点缓冲区满了,就会立即执行opengl的渲染,所以当Sprite足够多时,虽然他们使用的是同一张纹理,也会调用多次渲染。
CCRender.cpp
void Renderer::drawBatchedTriangles()
{
if(_queuedTriangleCommands.empty())
return;
CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_TRIANGLES");
_filledVertex = 0;
_filledIndex = 0;
/************** 1: Setup up vertices/indices *************/
_triBatchesToDraw[0].offset = 0;
_triBatchesToDraw[0].indicesToDraw = 0;
_triBatchesToDraw[0].cmd = nullptr;
int batchesTotal = 0;
int prevMaterialID = -1;
bool firstCommand = true;
for(auto it = std::begin(_queuedTriangleCommands); it != std::end(_queuedTriangleCommands); ++it)
{
const auto& cmd = *it;
auto currentMaterialID = cmd->getMaterialID();
const bool batchable = !cmd->isSkipBatching();
fillVerticesAndIndices(cmd);
// in the same batch ?
if (batchable && (prevMaterialID == currentMaterialID || firstCommand))
{
CC_ASSERT(firstCommand || _triBatchesToDraw[batchesTotal].cmd->getMaterialID() == cmd->getMaterialID() && "argh... error in logic");
_triBatchesToDraw[batchesTotal].indicesToDraw += cmd->getIndexCount();
_triBatchesToDraw[batchesTotal].cmd = cmd;
}
else
{
// is this the first one?
if (!firstCommand) {
batchesTotal++;
_triBatchesToDraw[batchesTotal].offset = _triBatchesToDraw[batchesTotal-1].offset + _triBatchesToDraw[batchesTotal-1].indicesToDraw;
}
_triBatchesToDraw[batchesTotal].cmd = cmd;
_triBatchesToDraw[batchesTotal].indicesToDraw = (int) cmd->getIndexCount();
// is this a single batch ? Prevent creating a batch group then
if (!batchable)
currentMaterialID = -1;
}
// capacity full ?
if (batchesTotal + 1 >= _triBatchesToDrawCapacity) {
_triBatchesToDrawCapacity *= 1.4;
_triBatchesToDraw = (TriBatchToDraw*) realloc(_triBatchesToDraw,sizeof(_triBatchesToDraw[0]) * _triBatchesToDrawCapacity);
}
prevMaterialID = currentMaterialID;
firstCommand = false;
}
batchesTotal++;
/************** 2: Copy vertices/indices to GL objects *************/
auto conf = Configuration::getInstance();
if (conf->supportsShareableVAO() && conf->supportsMapBuffer())
{
//Bind VAO
GL::bindVAO(_buffersVAO);
//Set VBO data
glBindBuffer(GL_ARRAY_BUFFER,_buffersVBO[0]);
// option 1: subdata
// glBufferSubData(GL_ARRAY_BUFFER,sizeof(_quads[0])*start,sizeof(_quads[0]) * n,&_quads[start] );
// option 2: data
// glBufferData(GL_ARRAY_BUFFER,sizeof(_verts[0]) * _filledVertex,_verts,GL_STATIC_DRAW);
// option 3: orphaning + glMapBuffer
// FIXME: in order to work as fast as possible,it must "and the exact same size and usage hints it had before."
// source: https://www.opengl.org/wiki/Buffer_Object_Streaming#Explicit_multiple_buffering
// so most probably we won't have any benefit of using it
glBufferData(GL_ARRAY_BUFFER,sizeof(_verts[0]) * _filledVertex,nullptr,GL_STATIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER,GL_WRITE_ONLY);
memcpy(buf,sizeof(_verts[0]) * _filledVertex);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_buffersVBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(_indices[0]) * _filledIndex,_indices,GL_STATIC_DRAW);
}
else
{
// Client Side Arrays
#define kQuadSize sizeof(_verts[0])
glBindBuffer(GL_ARRAY_BUFFER,_buffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER,GL_DYNAMIC_DRAW);
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
// vertices
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION,3,GL_FLOAT,GL_FALSE,kQuadSize,(GLvoid*) offsetof(V3F_C4B_T2F,vertices));
// colors
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,4,GL_UNSIGNED_BYTE,GL_TRUE,colors));
// tex coords
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD,2,texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,GL_STATIC_DRAW);
}
/************** 3: Draw *************/
for (int i=0; i<batchesTotal; ++i)
{
CC_ASSERT(_triBatchesToDraw[i].cmd && "Invalid batch");
_triBatchesToDraw[i].cmd->useMaterial();
glDrawElements(GL_TRIANGLES,(GLsizei) _triBatchesToDraw[i].indicesToDraw,GL_UNSIGNED_SHORT,(GLvoid*) (_triBatchesToDraw[i].offset*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += _triBatchesToDraw[i].indicesToDraw;
}
/************** 4: Cleanup *************/
if (Configuration::getInstance()->supportsShareableVAO())
{
//Unbind VAO
GL::bindVAO(0);
}
else
{
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
}
_queuedTriangleCommands.clear();
_filledVertex = 0;
_filledIndex = 0;
}
这个函数就是用来绘制TriangleCommand指令的,这里出现了一个新的类型TriBatchToDraw
struct TriBatchToDraw {
TrianglesCommand* cmd; // needed for the Material
GLushort indicesToDraw;
GLushort offset;
};
TriBatchToDraw 有三个成员变量,渲染指令cmd,需要渲染的顶点数量indicesToDraw,顶点数据在顶点缓冲区初始的偏移位置offset 如果一个渲染指令的MaterialID和上一个指令的MaterialID相等,就会把两条渲染指令整合到一起,如果两条指令使用了相同的MaterialID但是两条指令不连续,就不会整合到一个渲染指令里。 所以cocos2dx的Sprite使用同一张纹理时,必须是连续添加到父节点,中间不会有使用其他纹理的Sprite添加到父节点,才会达到SpriteBatchNode的效果