cocos2d-iphoneccspritebatchnode

understanding when to use CCSpriteBatchNodes?


I have seen in several places, including the source code of CCSpriteBatchNode that it is "expensive" to add/remove childs from it. My understanding is that the whole point of using batch nodes is to prevent expensive OpenGL calls from happening over and over when many sprites from the same sprite sheet are being added to the same container.

What I am wondering is 1) how "expensive" is adding / removing childs to a sprite batch node, and 2) when is it considered a appropriate to make use of one?

For example, I have a laser object which creates ten sprites... as it moves across the screen, it shows/hides the current sprite for that given screen position. When it reaches the far right edge of the screen, the laser object is discarded, and so are the ten sprites. So, I was wondering, is this a case where a sprite batch node would be not appropriate to use because it's only 10 sprites, and it happens so fast-- The move animation is 0.2 seconds, so that if the player were to rapidly fire, that would mean adding/removing 10 sprites to a batch node over and over...

In other cases, I have a SpriteBatchNode already setup for various objects, and occasionally I come across a one-off sprite that needs to be added, and it just happens to be part of the same sprite sheet, so I am tempted to add it to that batch node since it's there, and it's designated to that particular sprite sheet already...... Anyway, I'd love to get some clarification on this topic.


Solution

  • The main difference between a CCSpriteBatchNode and a normal CCSprite is the fact that a CCSpriteBatchNode sends all the data of all the sprites at once to the GPU instead that doing it for each sprite.

    A CCSprite draw call works in the following way:

    glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    

    Basically 3 calls are made to set the data of the sprite and then a call to glDrawArrays is done. If you have 100 sprite this code is executed 100 times.

    Now let's look at CCSpriteBatchNode (I chose the implementation without VAO, which is another possible optimization):

    glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, vertices));
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, colors));
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, texCoords));
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffersVBO_[1]);
    glDrawElements(GL_TRIANGLE_STRIP, (GLsizei) n*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(indices_[0])) );
    

    Now this code sets all the data of all the sprites at once, since it's stored in contiguous memory. This call is the same for 1, 10, 100, whatever amount of sprites.

    That's why it is more efficient, but at the same time, since the data is stored contiguosly in memory, when a child is removed or added or modifiyed, the array must be changed accordingly and updated in the GPU. That's where the cost of adding and removing comes from (or even the fact that a hidden CCSprite just skips the rendering phase while a hidden CCSprite in a batch node doesn't)

    From personal experience I can tell you that the cost is usually negligible and you should always use a CCSpriteBatchNode when you can (since they have their limits, like blending over the whole node and not on a per sprite basis and similar things) and when you are drawing more than a bunch of sprites of the same kind/reason.

    Benchmarking it for your self should be easy though.