COCOS Source Code Analysis – SpritebatchNode Drawing Principle

SpriteBatchNode inherits from Node and implements the TextureProtocol interface, rewriting Node’s addChild() method, visit() method and draw() method.

The addChild() method restricts its child element to only Sprite,

And the child element and SpriteBatchNode must use the same Texture2D object.

visit() is used to prevent the element from traversing down, and hand over the drawing work of all child elements to yourself.

The draw() method uses BatchNodeCommand to send drawing commands to the RenderQueue, thereby batch drawing all child elements.

SpriteBatchNode uses TextureAtlas to store the vertex information of all child sprites. TextureAtlas contains a V3F_C4B_T2F_Quad array and a Texture2D object, providing functions such as adding, deleting, modifying, and sorting the quads array. In this way, the main thing SpriteBatchNode does is to store the vertex information related to the child elements in the TextureAtlas. Finally, TextureAtlas provides a method to draw quads,

BatchCommand is drawn by the drawQuads() method

Code flow:

1 call

< div class="code">

 auto spBatchNode = SpriteBatchNode::create("bbb.png ");

spBatchNode
->setPosition(Point::ZERO);
scene
->addChild(spBatchNode);
spBatchNode
->setPosition(200,200);
// spBatchNode->setScale(0.1);

auto sp
= Sprite::createWithTexture(spBatchNode->getTexture());
sp
->setPosition(0,0);
spBatchNode
->addChild(sp,1);

sp
= Sprite::createWithTexture(spBatchNode->getTexture());
sp
->setPosition(10,10);
spBatchNode
->addChild(sp,-1);

2 Take a look at SpriteBatchNode Internal method

Initialization method

bool SpriteBatchNode::initWithTexture(Texture2D * tex, ssize_t capacity)

{
CCASSERT(capacity
>=0, "Capacity must be >= 0") ;

_blendFunc
= BlendFunc::ALPHA_PREMULTIPLIED;

if(tex->hasPremultipliedAlpha())//alpha premultiplied, ignored
{
_blendFunc
= BlendFunc::ALPHA_NON_PREMULTIPLIED;
}

//Use textureAtlas to store the vertex information of all child sprites. Add, delete, modify sorting of quads array, etc.
_textureAtlas = new TextureAtlas();

if (capacity == 0)
{
capacity
= DEFAULT_CAPACITY;
}

_textureAtlas
->initWithTexture(tex, capacity);

updateBlendFunc();

_children.reserve(capacity);
//Allocation space size
_descendants.reserve(capacity);
//Define your own shader
setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR));
return true;
}

How to add child elements:

void SpriteBatchNode::addChild(Node * child, int zOrder, const std::string &name)

{
CCASSERT(child
!= nullptr, "child should not be null< /span>");
CCASSERT(dynamic_cast
(child) != nullptr, "CCSpriteBatchNode only supports Sprites as children");
Sprite
*sprite = static_cast(child);
// check Sprite is using the same texture id
CCASSERT(sprite->getTexture()->getName() == _textureAtlas->getTexture()->getName(), "CCSprite is not using the same texture id");

Node::addChild(child, zOrder, name);

appendChild(sprite);
}
// addChild helper, faster than insertChild
void SpriteBatchNode::appendChild(Sprite* sprite)
{
_reorderChildDirty
=true;
sprite
->setBatchNode(this);
sprite
->setDirty(true);

if(_textureAtlas->getTotalQuads() == _textureAtlas->getCapacity ()) {
increaseAtlasCapacity();
//Resize the container,
}

_descendants.push_back(sprite);
//All child nodes.
int index = static_cast<int>(_descendants.size()- 1);

sprite
->setAtlasIndex(index);//Set the rendering of sprite The node is the index data of TextureAtlas

V3F_C4B_T2F_Quad quad
= sprite->getQuad();
//Put the vertex information of the new sprite in the textureAtlas for drawing Use, the vertex coordinates at this time are still the coordinates in the father, not the world coordinates
_textureAtlas->insertQuad(&quad, index);//pass the sprite vertex data Enter the index position of TextureAtlas.

// add children recursively
auto& children = sprite->getChildren();
for(const auto &child: children) {< span style="color: #008000;">//
recursive call
appendChild(static_cast(child)); //Add all the sprite The data is added to descendants.
}
}

The traversal method visit

 // override visit

// don't call visit on it's children span>
void SpriteBatchNode::visit(Renderer *renderer, const Mat4 &< span style="color: #000000;">parentTransform, uint32_t parentFlags)
{
CC_PROFILER_START_CATEGORY(kProfilerCategoryBatchSprite, "CCSpriteBatchNode-visit");

// CAREFUL:
// This visit is almost identical to CocosNode#visit
// with the exception that it doesn't call visit on it 's children
//
// The alternative is to have a void Sprite#visit, but
// although this is less maintainable, is faster
//
if (! _visible)
{
return;
}
//The sorting is pretty good
sortAllChildren();
//Get the matrix of local coordinate conversion to world coordinate
uint32_t flags = processParentFlags(parentTransform, parentFlags);

// IMPORTANT:
// To ease the migration to v3.0, we still support the Mat4 stack,
// but it is deprecated and your code should not rely on it
Director* director = Director::getInstance();
director
->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
director
->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);

draw(renderer, _modelViewTransform, flags);

director
->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
// FIX ME: Why need to set _orderOfArrival to 0??
// Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
// setOrderOfArrival(0);

CC_PROFILER_STOP_CATEGORY(kProfilerCategoryBatchSprite,
"CCSpriteBatchNode-visit");
}

//override sortAllChildren

//Sort function
void SpriteBatchNode::sortAllChildren()
{
if (_reorderChildDirty)
{
std::sort(std::begin(_children), std::end(_children), nodeComparisonLess);

//sorted now check all children
if (!_children.empty())
{
//first sort all children recursively based on zOrder
for(const auto &child: _children) {
child
->sortAllChildren();
}

ssize_t index
=0;

//fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact)
// and at the same time reorder descendants and the quads to the right index
/*
//Quickly send, give each child a new atlasIndex according to the relative zOrder of each child (keep the parent -> child relationship unchanged)
// Reorder the descendants array and quad to the correct index at the same time
*/
//The childern at this time is the array sorted from small to large according to localZ span>
for(const auto &child: _children) {
Sprite
* sp = static_cast(child);
updateAtlasIndex(sp,
&index);
}
}

_reorderChildDirty
=false;
}
}
//Update the index of atlas data of sprite and sprite child nodes.
void SpriteBatchNode::updateAtlasIndex(Sprite* sprite, ssize_t* curIndex)
{
auto
& array = sprite->getChildren();
auto count
= array.size();

ssize_t oldIndex
= 0;
/*
In sortAllChildren, the children are sorted according to localZ, from small to large, because the previous AtlasIndex is assigned in order, so the AtlasIndex must be reset
*/
//If there are no child nodes, then the sprite is the curIndex position
if( count == 0)
{
oldIndex
= sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);//re-increment assignment
sprite->setOrderOfArrival(0);

if (oldIndex != *curIndex){
//Update the position in _textureAtlas etc.
swap(oldIndex, *curIndex);
}
(
*curIndex)++;
}
else
{
bool needNewIndex=true;//Is it necessary to assign a new index
//First localZ<0, then sprite, and finally localZ> 0
//Because all the child nodes are sorted first. First localZ<0, then localZ>0

//If the first localZ>=0 then set the sprite first Index
if (array.at(0)->getLocalZOrder()> = 0)
{
//The first child of sprite local is greater than 0, and the remaining The children below are all greater than 0, so the children draw on themselves
//all children are in front of the parent
oldIndex = sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);
sprite
->setOrderOfArrival(0);
if (oldIndex != *curIndex)
{
swap(oldIndex,
*curIndex);
}
(
*curIndex)++;

needNewIndex
= false;//No need for a new index, just traverse in order afterwards
}

for(const auto &child: array) {
Sprite
* sp = static_cast(child);
// Found the first localZorder>0, set the sprite .
if (needNewIndex && sp->getLocalZOrder() >= 0< span style="color: #000000;">)
{
oldIndex = sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);
sprite
->setOrderOfArrival(0);
if (oldIndex != *curIndex) {
this->swap(oldIndex, *curIndex);
}
(
*curIndex)++;
needNewIndex
= false;
}
//recursive call
updateAtlasIndex(sp, curIndex);
}

//This situation is: all child nodes are localZ <0.
//all children have a zOrder <0)
/*
The sprite’s children have been traversed, and finally it’s the sprite’s turn
*/
if (needNewIndex)
{
oldIndex
= sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);
sprite
->setOrderOfArrival(0);
if (oldIndex != *curIndex) {
swap(oldIndex,
*curIndex);
}
(
*curIndex)++;
}
}
}

swap updates the newly sorted vertex data to quads to provide basic data for opengl

void SpriteBatchNode::swap(ssize_t oldIndex, ssize_t newIndex)

{
CCASSERT(oldIndex
>=0 && oldIndex <(int)_descendants.size() && newIndex >=0 && newIndex <(int) _descendants.size(), "Invalid index");

V3F_C4B_T2F_Quad
* quads = _textureAtlas->getQuads();
std::swap( quads[oldIndex], quads[newIndex] );

//update the index of other swapped item

auto oldIt
= std::next( _descendants.begin(), oldIndex );
auto newIt
= std::next( _descendants.begin(), newIndex );

(
*newIt)->setAtlasIndex(oldIndex);
// (*oldIt)->setAtlasIndex(newIndex);< /span>

std::swap(
*oldIt, *newIt );
}

The new element draw method is not really drawing, but putting the drawing command in _batchCommand

void SpriteBatchNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)

{
// Optimization: Fast Dispatch
if( _textureAtlas->getTotalQuads() == 0)
{
return;
}

for(const auto &child: _children)
{
//Update the child’s coordinates, recursively
child->updateTransform();
}


_batchCommand.init(
_globalZOrder,
getGLProgram(),
_blendFunc,
_textureAtlas,
transform);
renderer
->addCommand(&_batchCommand);
}

 auto spBatchNode = SpriteBatchNode::create("bbb.png" );

spBatchNode
->setPosition(Point::ZERO);
scene
->addChild(spBatchNode);
spBatchNode
->setPosition(200,200);
// spBatchNode->setScale(0.1);

auto sp
= Sprite::createWithTexture(spBatchNode->getTexture());
sp
->setPosition(0,0);
spBatchNode
->addChild(sp,1);

sp
= Sprite::createWithTexture(spBatchNode->getTexture());
sp
->setPosition(10,10);
spBatchNode
->addChild(sp,-1);

< span style="color: #0000ff;">bool SpriteBatchNode::initWithTexture(Texture2D *tex, ssize_t capacity)

{
CCASSERT(capacity
>=0, "Capacity must be >= 0") ;

_blendFunc
= BlendFunc::ALPHA_PREMULTIPLIED;

if(tex->hasPremultipliedAlpha())//alpha premultiplied, ignored
{
_blendFunc
= BlendFunc::ALPHA_NON_PREMULTIPLIED;
}

//Use textureAtlas to store the vertex information of all child sprites. Add, delete, modify sorting of quads array, etc.
_textureAtlas = new TextureAtlas();

if (capacity == 0)
{
capacity
= DEFAULT_CAPACITY;
}

_textureAtlas
->initWithTexture(tex, capacity);

updateBlendFunc();

_children.reserve(capacity);
//Allocation space size
_descendants.reserve(capacity);
//Define your own shader
setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR));
return true;
}

void SpriteBatchNode::addChild(Node * child, < span style="color: #0000ff;">int zOrder, const std:: string &name)

{
CCASSERT(child
!= nullptr, "child should not be null< /span>");
CCASSERT(dynamic_cast
(child) != nullptr, "CCSpriteBatchNode only supports Sprites as children");
Sprite
*sprite = static_cast(child);
// check Sprite is using the same texture id
CCASSERT(sprite->getTexture()->getName() == _textureAtlas->getTexture()->getName(), "CCSprite is not using the same texture id");

Node::addChild(child, zOrder, name);

appendChild(sprite);
}
// addChild helper, faster than insertChild
void SpriteBatchNode::appendChild(Sprite* sprite)
{
_reorderChildDirty
=true;
sprite
->setBatchNode(this);
sprite
->setDirty(true);

if(_textureAtlas->getTotalQuads() == _textureAtlas->getCapacity ()) {
increaseAtlasCapacity();
//Resize the container,
}

_descendants.push_back(sprite);
//All child nodes.
int index = static_cast<int>(_descendants.size()- 1);

sprite
->setAtlasIndex(index);//Set sprite rendering The node is the index data of TextureAtlas

V3F_C4B_T2F_Quad quad
= sprite->getQuad();
//Put the vertex information of the new sprite in the textureAtlas for drawing Use, the vertex coordinates at this time are still the coordinates in the father, not the world coordinates
_textureAtlas->insertQuad(&quad, index);//pass the sprite vertex data Enter the index position of TextureAtlas.

// add children recursively
auto& children = sprite->getChildren();
for(const auto &child: children) {< span style="color: #008000;">//
recursive call
appendChild(static_cast(child)); //Add all the sprite The data is added to descendants.
}
}

// override visit

// don't call visit on it's children span>
void SpriteBatchNode::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
CC_PROFILER_START_CATEGORY(kProfilerCategoryBatchSprite,
"CCSpriteBatchNode - visit");

// CAREFUL:
// This visit is almost identical to CocosNode#visit
// with the exception that it doesn‘t call visit on it‘s children
//
// The alternative is to have a void Sprite#visit, but
// although this is less maintainable, is faster
//
if (! _visible)
{
return;
}
//排序,写的挺不错
sortAllChildren();
//得到本地坐标转换世界坐标的矩阵
uint32_t flags = processParentFlags(parentTransform, parentFlags);

// IMPORTANT:
// To ease the migration to v3.0, we still support the Mat4 stack,
// but it is deprecated and your code should not rely on it
Director* director = Director::getInstance();
director
->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
director
->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);

draw(renderer, _modelViewTransform, flags);

director
->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
// FIX ME: Why need to set _orderOfArrival to 0??
// Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
// setOrderOfArrival(0);

CC_PROFILER_STOP_CATEGORY(kProfilerCategoryBatchSprite,
"CCSpriteBatchNode - visit");
}

//override sortAllChildren

//排序功能
void SpriteBatchNode::sortAllChildren()
{
if (_reorderChildDirty)
{
std::sort(std::begin(_children), std::end(_children), nodeComparisonLess);

//sorted now check all children
if (!_children.empty())
{
//first sort all children recursively based on zOrder
for(const auto &child: _children) {
child
->sortAllChildren();
}

ssize_t index
=0;

//fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact)
// and at the same time reorder descendants and the quads to the right index
/*
//快速发送,根据每个孩子的相对zOrder给每个孩子一个新的atlasIndex(保持父母 - >孩子的关系不变)
       //同时重新排序descendants数组和四边形到正确的索引
*/
//此时的childern为根据localZ从小到大排序之后的数组
for(const auto &child: _children) {
Sprite
* sp = static_cast(child);
updateAtlasIndex(sp,
&index);
}
}

_reorderChildDirty
=false;
}
}
//更新sprite和sprite子节点的atlas数据的index。
void SpriteBatchNode::updateAtlasIndex(Sprite* sprite, ssize_t* curIndex)
{
auto
& array = sprite->getChildren();
auto count
= array.size();

ssize_t oldIndex
= 0;
/*
在sortAllChildren,根据localZ对children进行了排序,从小到大排序,因为之前的AtlasIndex为顺序赋值的,所以要重新设置AtlasIndex
*/
//如果没有子节点,那么sprite就是curIndex位置了
if( count == 0 )
{
oldIndex
= sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);//重新递增赋值
sprite->setOrderOfArrival(0);

if (oldIndex != *curIndex){
//更新在_textureAtlas等中的位置
swap(oldIndex, *curIndex);
}
(
*curIndex)++;
}
else
{
bool needNewIndex=true;//是否需要赋值新的索引
//先localZ<0的,然后sprite,最后localZ>0
//因为先对所有的子节点排序过。先localZ<0,然后localZ>0

//如果第一个localZ>=0那么先设置sprite的index
if (array.at(0)->getLocalZOrder() >= 0)
{
//sprite的第一个孩子local都大于0,剩下的孩子都大于0,所以孩子绘制都在自己上面
//all children are in front of the parent
oldIndex = sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);
sprite
->setOrderOfArrival(0);
if (oldIndex != *curIndex)
{
swap(oldIndex,
*curIndex);
}
(
*curIndex)++;

needNewIndex
= false;//不需要新的索引,后面的按照顺序遍历就行
}

for(const auto &child: array) {
Sprite
* sp = static_cast(child);
//找到了第一个localZorder>0的,设置sprite。
if (needNewIndex && sp->getLocalZOrder() >= 0)
{
oldIndex
= sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);
sprite
->setOrderOfArrival(0);
if (oldIndex != *curIndex) {
this->swap(oldIndex, *curIndex);
}
(
*curIndex)++;
needNewIndex
= false;
}
//递归调用
updateAtlasIndex(sp, curIndex);
}

//这种情况是:所有的子节点都是localZ<0。
//all children have a zOrder < 0)
/*
sprite的children已经遍历完成,最后轮到sprite自己
*/
if (needNewIndex)
{
oldIndex
= sprite->getAtlasIndex();
sprite
->setAtlasIndex(*curIndex);
sprite
->setOrderOfArrival(0);
if (oldIndex != *curIndex) {
swap(oldIndex,
*curIndex);
}
(
*curIndex)++;
}
}
}

void SpriteBatchNode::swap(ssize_t oldIndex, ssize_t newIndex)

{
CCASSERT(oldIndex
>=0 && oldIndex < (int)_descendants.size() && newIndex >=0 && newIndex < (int)_descendants.size(), "Invalid index");

V3F_C4B_T2F_Quad
* quads = _textureAtlas->getQuads();
std::swap( quads[oldIndex], quads[newIndex] );

//update the index of other swapped item

auto oldIt
= std::next( _descendants.begin(), oldIndex );
auto newIt
= std::next( _descendants.begin(), newIndex );

(
*newIt)->setAtlasIndex(oldIndex);
// (*oldIt)->setAtlasIndex(newIndex);

std::swap(
*oldIt, *newIt );
}

void SpriteBatchNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)

{
// Optimization: Fast Dispatch
if( _textureAtlas->getTotalQuads() == 0 )
{
return;
}

for(const auto &child: _children)
{
//更新孩子的坐标,递归
child->updateTransform();
}


_batchCommand.init(
_globalZOrder,
getGLProgram(),
_blendFunc,
_textureAtlas,
transform);
renderer
->addCommand(&_batchCommand);
}

Leave a Comment

Your email address will not be published.