1:TextureAtlas
头文件:
GLushort* _indices; //顶点index
GLuint _VAOname;
GLuint _buffersVBO[2]; //0: vertex 1: indices
bool _dirty; //indicates whether or not the array buffer of the VBO needs to be updated
/** quantity of quads that are going to be drawn */
ssize_t _totalQuads; //所有的顶点数量
/** quantity of quads that can be stored with the current texture atlas size */
ssize_t _capacity;
/** Texture of the texture atlas */
Texture2D* _texture;
/** Quads that are going to be rendered */
V3F_C4B_T2F_Quad* _quads; //顶点数据
源文件代码分析:
``bool TextureAtlas::initWithTexture(Texture2D *texture, ssize_t capacity)
{
CCASSERT(capacity>=0, "Capacity must be >= 0");
// CCASSERT(texture != nullptr, "texture should not be null");
_capacity = capacity; //预留的顶点数量
_totalQuads = 0;
// retained in property
this->_texture = texture;
CC_SAFE_RETAIN(_texture);
// Re-initialization is not allowed
CCASSERT(_quads == nullptr && _indices == nullptr, "");
//给预留的顶点数量分配内存。每个顶点数据包括(位置---V3F,颜色---C4B,uv值---T2F)
_quads = (V3F_C4B_T2F_Quad*)malloc( _capacity * sizeof(V3F_C4B_T2F_Quad) );
//所有构成三角形的顶点index,4个顶点有6个index(两个三角形)
_indices = (GLushort *)malloc( _capacity * 6 * sizeof(GLushort) );
if( ! ( _quads && _indices) && _capacity > 0)
{
//CCLOG("cocos2d: TextureAtlas: not enough memory");
CC_SAFE_FREE(_quads);
CC_SAFE_FREE(_indices);
// release texture, should set it to null, because the destruction will
// release it too. see cocos2d-x issue #484
CC_SAFE_RELEASE_NULL(_texture);
return false;
}
memset( _quads, 0, _capacity * sizeof(V3F_C4B_T2F_Quad) );
memset( _indices, 0, _capacity * 6 * sizeof(GLushort) );
//.. ...省略部分代码
return true;
}
void TextureAtlas::setupIndices()
{
if (_capacity == 0)
return;
for( int i=0; i < _capacity; i++)
{
_indices[i*6+0] = i*4+0; //第i*4+0个顶点构成2*i个三角形的第一个顶点
_indices[i*6+1] = i*4+1; //第i*4+1个顶点构成2*i个三角形的第二个顶点
_indices[i*6+2] = i*4+2; //第i*4+2个顶点构成2*i个三角形的第三个顶点
// inverted index. issue #179
_indices[i*6+3] = i*4+3; //第i*4+3个顶点构成2*i+1个三角形的第一个顶点
_indices[i*6+4] = i*4+2; //第i*4+2个顶点构成2*i+1个三角形的第二个顶点
_indices[i*6+5] = i*4+1; //第i*4+1个顶点构成2*i+1个三角形的第三个顶点
}
}
``
//将quad插入到所有数据的index位置中
void TextureAtlas::insertQuad(V3F_C4B_T2F_Quad *quad, ssize_t index)
{
CCASSERT( index>=0 && index<_capacity, "insertQuadWithTexture: Invalid index");
_totalQuads++; //顶点数量+1
CCASSERT( _totalQuads <= _capacity, "invalid totalQuads");
// issue #575. index can be > totalQuads
auto remaining = (_totalQuads-1) - index; //将index后面的remaining个顶点往后移。
// last object doesn't need to be moved
if( remaining > 0)
{
// texture coordinates
//移动remaining个数据往后移动。
memmove( &_quads[index+1],&_quads[index], sizeof(_quads[0]) * remaining );
}
_quads[index] = *quad;
_dirty = true;
}
//将quads的amount个数据插入到所有数据的index位置上。
void TextureAtlas::insertQuads(V3F_C4B_T2F_Quad* quads, ssize_t index, ssize_t amount)
{
CCASSERT(index>=0 && amount>=0 && index+amount<=_capacity, "insertQuadWithTexture: Invalid index + amount");
_totalQuads += amount; //顶点数据+amount
CCASSERT( _totalQuads <= _capacity, "invalid totalQuads");
// issue #575. index can be > totalQuads
auto remaining = (_totalQuads-1) - index - amount;
// last object doesn't need to be moved
if( remaining > 0)
{
// tex coordinates
//将index后面的所有数据向后移动amount个位置
memmove( &_quads[index+amount],&_quads[index], sizeof(_quads[0]) * remaining );
}
//设置index后面的数据
auto max = index + amount;
int j = 0;
for (ssize_t i = index; i < max ; i++)
{
_quads[index] = quads[j];
index++;
j++;
}
_dirty = true;
}
//将oldIndex位置的数据插入到newIndex的位置。并且中间的数据一并移动。
//oldIndex ,A,A,A,A,A..,newIndex---->A,A,A,A,A..,newIndex,oldIndex。
//newIndex,A,A,A,A,A..,oldIndex---->oldIndex,newIndex,A,A,A,A,A..。
void TextureAtlas::insertQuadFromIndex(ssize_t oldIndex, ssize_t newIndex)
{
CCASSERT( newIndex >= 0 && newIndex < _totalQuads, "insertQuadFromIndex:atIndex: Invalid index");
CCASSERT( oldIndex >= 0 && oldIndex < _totalQuads, "insertQuadFromIndex:atIndex: Invalid index");
if( oldIndex == newIndex )
{
return;
}
// because it is ambiguous in iphone, so we implement abs ourselves
// unsigned int howMany = abs( oldIndex - newIndex);
auto howMany = (oldIndex - newIndex) > 0 ? (oldIndex - newIndex) : (newIndex - oldIndex);
auto dst = oldIndex;
auto src = oldIndex + 1;
if( oldIndex > newIndex)
{
dst = newIndex+1;
src = newIndex;
}
// texture coordinates
V3F_C4B_T2F_Quad quadsBackup = _quads[oldIndex];
memmove( &_quads[dst],&_quads[src], sizeof(_quads[0]) * howMany );
_quads[newIndex] = quadsBackup;
_dirty = true;
}
//删除index的数据,并且将index后面的 数据往前移动一个。
void TextureAtlas::removeQuadAtIndex(ssize_t index)
{
CCASSERT( index>=0 && index<_totalQuads, "removeQuadAtIndex: Invalid index");
auto remaining = (_totalQuads-1) - index;
// last object doesn't need to be moved
if( remaining )
{
// texture coordinates
memmove( &_quads[index],&_quads[index+1], sizeof(_quads[0]) * remaining );
}
_totalQuads--;
_dirty = true;
}
//删除index开始的amount个数据,后面的数据往前移动
void TextureAtlas::removeQuadsAtIndex(ssize_t index, ssize_t amount)
{
CCASSERT(index>=0 && amount>=0 && index+amount<=_totalQuads, "removeQuadAtIndex: index + amount out of bounds");
auto remaining = (_totalQuads) - (index + amount);
_totalQuads -= amount;
if ( remaining )
{
memmove( &_quads[index], &_quads[index+amount], sizeof(_quads[0]) * remaining );
}
_dirty = true;
}
//移动oldIndex开始的amount到newIndex位置。
void TextureAtlas::moveQuadsFromIndex(ssize_t oldIndex, ssize_t amount, ssize_t newIndex)
{
CCASSERT(oldIndex>=0 && amount>=0 && newIndex>=0, "values must be >= 0");
CCASSERT(newIndex + amount <= _totalQuads, "insertQuadFromIndex:atIndex: Invalid index");
CCASSERT(oldIndex < _totalQuads, "insertQuadFromIndex:atIndex: Invalid index");
if( oldIndex == newIndex )
{
return;
}
//create buffer
size_t quadSize = sizeof(V3F_C4B_T2F_Quad);
V3F_C4B_T2F_Quad* tempQuads = (V3F_C4B_T2F_Quad*)malloc( quadSize * amount);
memcpy( tempQuads, &_quads[oldIndex], quadSize * amount );
if (newIndex < oldIndex)
{
// move quads from newIndex to newIndex + amount to make room for buffer
memmove( &_quads[newIndex], &_quads[newIndex+amount], (oldIndex-newIndex)*quadSize);
}
else
{
// move quads above back
memmove( &_quads[oldIndex], &_quads[oldIndex+amount], (newIndex-oldIndex)*quadSize);
}
memcpy( &_quads[newIndex], tempQuads, amount*quadSize);
free(tempQuads);
_dirty = true;
}
//渲染start位置的numberOfQuads的数据
void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start)
{
CCASSERT(numberOfQuads>=0 && start>=0, "numberOfQuads and start must be >= 0");
if(!numberOfQuads)
return;
GL::bindTexture2D(_texture->getName());
if (Configuration::getInstance()->supportsShareableVAO())
{
//
// Using VBO and VAO
//
// FIXME:: update is done in draw... perhaps it should be done in a timer
if (_dirty)
{
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]) * _capacity, nullptr, GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quads, sizeof(_quads[0])* _totalQuads);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
_dirty = false;
}
GL::bindVAO(_VAOname);
#if CC_REBIND_INDICES_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
#endif
glDrawElements(GL_TRIANGLES, (GLsizei) numberOfQuads*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(_indices[0])) );
GL::bindVAO(0);
#if CC_REBIND_INDICES_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif
// glBindVertexArray(0);
}
else
{
//
// Using VBO without VAO
//
#define kQuadSize sizeof(_quads[0].bl)
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
// FIXME:: update is done in draw... perhaps it should be done in a timer
if (_dirty)
{
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(_quads[0]) * _totalQuads , &_quads[0] );
_dirty = false;
}
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]);
glDrawElements(GL_TRIANGLES, (GLsizei)numberOfQuads*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(_indices[0])));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,numberOfQuads*6);
CHECK_GL_ERROR_DEBUG();
}
2:SpriteBatchNode
// addChild helper, faster than insertChild
void SpriteBatchNode::appendChild(Sprite* sprite)
{
_reorderChildDirty=true;
sprite->setBatchNode(this);
sprite->setDirty(true);
if(_textureAtlas->getTotalQuads() == _textureAtlas->getCapacity()) {
increaseAtlasCapacity();
}
_descendants.push_back(sprite);//所有的子节点。
int index = static_cast<int>(_descendants.size()-1);
sprite->setAtlasIndex(index); 设置sprite的渲染节点为TextureAtlas的第index个数据
V3F_C4B_T2F_Quad quad = sprite->getQuad();
_textureAtlas->insertQuad(&quad, index);//将sprite的顶点数据传入到TextureAtlas的index位置。
// add children recursively
auto& children = sprite->getChildren();
for(const auto &child: children) {
appendChild(static_cast<Sprite*>(child)); //将sprite的所有数据加入到descendants。
}
}
//将sprite的数据插入到TextureAtlas的index位置。但是现在还不append到spriteBatchNode
void SpriteBatchNode::insertQuadFromSprite(Sprite *sprite, ssize_t index)
{
CCASSERT( sprite != nullptr, "Argument must be non-nullptr");
CCASSERT( dynamic_cast<Sprite*>(sprite), "CCSpriteBatchNode only supports Sprites as children");
// make needed room
while(index >= _textureAtlas->getCapacity() || _textureAtlas->getCapacity() == _textureAtlas->getTotalQuads())
{
this->increaseAtlasCapacity();
}
//
// update the quad directly. Don't add the sprite to the scene graph
//
sprite->setBatchNode(this);
sprite->setAtlasIndex(index);
V3F_C4B_T2F_Quad quad = sprite->getQuad();
_textureAtlas->insertQuad(&quad, index);
// FIXME:: updateTransform will update the textureAtlas too, using updateQuad.
// FIXME:: so, it should be AFTER the insertQuad
sprite->setDirty(true);
sprite->updateTransform();
}
//获取sprite的第一个atlas 数据的index
ssize_t SpriteBatchNode::atlasIndexForChild(Sprite *sprite, int nZ)
{
auto& siblings = sprite->getParent()->getChildren();
auto childIndex = siblings.getIndex(sprite);
// ignore parent Z if parent is spriteSheet
bool ignoreParent = (SpriteBatchNode*)(sprite->getParent()) == this;
Sprite *prev = nullptr;
if (childIndex > 0 && childIndex != -1)
{
prev = static_cast<Sprite*>(siblings.at(childIndex - 1));
}
// first child of the sprite sheet
if (ignoreParent)
{
if (childIndex == 0)
{
return 0;
}
return highestAtlasIndexInChild(prev) + 1; //之前兄弟节点的最后一个子节点的index +1
}
// parent is a Sprite, so, it must be taken into account
// first child of an Sprite ?
if (childIndex == 0)//第一个节点
{
Sprite *p = static_cast<Sprite*>(sprite->getParent());
// less than parent and brothers
if (nZ < 0)
{
return p->getAtlasIndex();//
}
else
{
return p->getAtlasIndex() + 1;//如果nz>=0,那么index是父节点的index +1。
}
}
else
{
// previous & sprite belong to the same branch
if ((prev->getLocalZOrder() < 0 && nZ < 0) || (prev->getLocalZOrder() >= 0 && nZ >= 0))
{
//prev和本 节点在同一批次,那么index是prev的最大index+1。
return highestAtlasIndexInChild(prev) + 1;
}
// else (previous < 0 and sprite >= 0 )
Sprite *p = static_cast<Sprite*>(sprite->getParent());//这种情况下,是父节点的index+1
return p->getAtlasIndex() + 1;
}
// Should not happen. Error calculating Z on SpriteSheet
CCASSERT(0, "should not run here");
return 0;
}
//sprite和sprite所有子节点的最大index
ssize_t SpriteBatchNode::highestAtlasIndexInChild(Sprite *sprite)
{
auto& children = sprite->getChildren();
if (children.size() == 0)
{
return sprite->getAtlasIndex();
}
else
{
return highestAtlasIndexInChild( static_cast<Sprite*>(children.back()));
}
}
//sprite和sprite所有子节点的最小index
ssize_t SpriteBatchNode::lowestAtlasIndexInChild(Sprite *sprite)
{
auto& children = sprite->getChildren();
if (children.size() == 0)
{
return sprite->getAtlasIndex();
}
else
{
return lowestAtlasIndexInChild(static_cast<Sprite*>(children.at(0)));
}
}
//更新sprite和sprite子节点的atlas数据的index。
void SpriteBatchNode::updateAtlasIndex(Sprite* sprite, ssize_t* curIndex)
{
auto& array = sprite->getChildren();
auto count = array.size();
ssize_t oldIndex = 0;
if( count == 0 )//如果没有子节点,那么sprite就是curIndex位置了
{
oldIndex = sprite->getAtlasIndex();
sprite->setAtlasIndex(*curIndex);
sprite->setOrderOfArrival(0);
if (oldIndex != *curIndex){
swap(oldIndex, *curIndex);
}
(*curIndex)++;
}
else
{
bool needNewIndex=true;
//先localZ<0的,然后sprite,最后localZ>0
//因为先对所有的子节点排序过。先localZ<0,然后localZ>0
if (array.at(0)->getLocalZOrder() >= 0)//如果第一个localZ>0那么先设置sprite的index
{
//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<Sprite*>(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。
if (needNewIndex)
{//all children have a zOrder < 0)
oldIndex = sprite->getAtlasIndex();
sprite->setAtlasIndex(*curIndex);
sprite->setOrderOfArrival(0);
if (oldIndex != *curIndex) {
swap(oldIndex, *curIndex);
}
(*curIndex)++;
}
}
}
本文详细解析了cocos2d-x中的TextureAtlas和SpriteBatchNode类的实现原理及关键方法,包括初始化、索引设置、顶点插入等操作,并介绍了如何利用这些类高效渲染大量精灵。
1045

被折叠的 条评论
为什么被折叠?



