功能:
这个命令主要用来绘制一个或多个矩形区域。相邻的QuedCommand如果使用相同的纹理,就可以实现自动批绘制。提高绘制效率。
条件:
1、不包含自定义的着色器全局变量。
2、使用相同的的shader程序。
3、使用同一张纹理。
4、使用相同的混合模式。
在顺序上相邻的且满足上面四个条件的sprite就可以自动实现批绘制了。
//Start drawing verties in batch
for(const auto& cmd : _batchedQuadCommands)
{
auto newMaterialID = cmd->getMaterialID();
if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH)
{
//Draw quads
if(quadsToDraw > 0)
{
glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += quadsToDraw*6;
startQuad += quadsToDraw;
quadsToDraw = 0;
}
//Use new material
cmd->useMaterial();
_lastMaterialID = newMaterialID;
}
quadsToDraw += cmd->getQuadCount();
}
实现原理:
Render在访问所以命令前会对他们进行排序,相同类型的命令就会排到一块,遍历命令时,再将相同的且满足自动批绘制条件的精灵加入到内存中,最后进行批绘制。
性能:
1、QuedCommand中使用建立VBO和VAO来提高绘制效率。关于VBO和VAO概念不懂的可以看这篇文章http://blog.youkuaiyun.com/bill_man/article/details/38314077。
2、使用glDrawElements进行一次性绘制,减少了函数调用。
if (Configuration::getInstance()->supportsShareableVAO())
{
//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(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW);
// option 3: orphaning + glMapBuffer
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quads, sizeof(_quads[0])* (_numQuads));
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//Bind VAO
GL::bindVAO(_quadVAO);
}
else
{
#define kQuadSize sizeof(_quads[0].bl)
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _numQuads , _quads, 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, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));
// tex coords
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
}
绘制时机:
1、因为所有顶点数据使用一个VBO,当Quad数量超过了VBO所能容纳的最大Quad的数量10922(65536 / 6),将会立即执行前面的绘制。
//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();
}
2、如果还没超过,但是转到了其他类型的命令,那么也会立即执行前面的绘制。在Flush2D里会执行。
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<CustomCommand*>(command);
cmd->execute();
}
else if(RenderCommand::Type::BATCH_COMMAND == commandType)
{
flush();
auto cmd = static_cast<BatchCommand*>(command);
cmd->execute();
}
else if (RenderCommand::Type::MESH_COMMAND == commandType)
{
flush2D();
auto cmd = static_cast<MeshCommand*>(command);
if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())
{
flush3D();
cmd->preBatchDraw();
cmd->batchDraw();
_lastBatchedMeshCommand = cmd;
}
else
{
cmd->batchDraw();
}
}
void Renderer::flush2D()
{
drawBatchedQuads();
_lastMaterialID = 0;
}
3、不满足自动批绘制条件的会直线先进行绘制。满足条件的则会加入到内存中,等到最后一次性绘制。
if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH)
{
//Draw quads
if(quadsToDraw > 0)
{
glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += quadsToDraw*6;
startQuad += quadsToDraw;
quadsToDraw = 0;
}
//Use new material
cmd->useMaterial();
_lastMaterialID = newMaterialID;
}
//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);
相似比较:
另外还有一个批绘制命令BatchCommand,这个命令其实一般是配合着SpriteBatchNode使用的,被加入到SpriteBatchNode里的精灵就能进行批绘制。
BatchCommand是用来绘制一个TextrueAtlasde ,如label,TileMap。
所以他们的区别
1、QuadCommand是可以自动批绘制,BatchCommand要手动添加
2、BatchCommand里,我们不能为每个Sprite单独设置相关的opengl ES 绘制参数(????),如混合模式等。所以,SpriteNode适合那些比较简单的模型,如Label,TileMap。
使用
_quadCommand.init(
_globalZOrder,
_textureAtlas->getTexture()->getName(),
getGLProgramState(),
_blendFunc,
_textureAtlas->getQuads(),
_quadsToDraw,
transform);
renderer->addCommand(&_quadCommand);