cocos2dx Sprite使用同一张纹理会只调用一次渲染

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的效果

相关文章

    本文实践自 RayWenderlich、Ali Hafizji 的文章《...
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...