SpriteBatchNode继承Node,并实现了TextureProtocol接口,重写了Node的addChild()方法,visit()方法以及draw()方法。
addChild()方法限制其子元素只能是Sprite,
并且子元素与SpriteBatchNode必须使用同一个Texture2D对象。
visit()用于阻止元素向下遍历,将所有子元素的绘制工作交给自己处理。
draw()方法使用BatchNodeCommand将绘制命令发送到RenderQueue,从而对所有子元素进行批绘制。
SpriteBatchNode使用TextureAtlas存储所有子精灵的顶点信息。TextureAtlas包含一个V3F_C4B_T2F_Quad数组和一个Texture2D对象,提供对quads数组的添加,删除,修改,排序等功能。这样,SpriteBatchNode所做的主要事情就是将与子元素相关的顶点信息存储到TextureAtlas中。最后,TextureAtlas提供了绘制quads的方法,
BatchCommand就是通过drawQuads()方法绘制的
代码流程:
1 调用
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 看一下SpriteBatchNode内部方法
初始化方法
bool SpriteBatchNode::initWithTexture(Texture2D *tex, ssize_t capacity) { CCASSERT(capacity>=0, "Capacity must be >= 0"); _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED; if(tex->hasPremultipliedAlpha())//alpha预乘,忽略 { _blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED; } //使用textureAtlas存储所有子精灵的顶点信息,通过对quads数组的添加,删除,修改排序等 _textureAtlas = new TextureAtlas(); if (capacity == 0) { capacity = DEFAULT_CAPACITY; } _textureAtlas->initWithTexture(tex, capacity); updateBlendFunc(); _children.reserve(capacity); //分配空间大小 _descendants.reserve(capacity); //定义属于自己的着色器 setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR)); return true; }
添加子元素方法:
void SpriteBatchNode::addChild(Node * child, int zOrder, const std::string &name) { CCASSERT(child != nullptr, "child should not be null"); CCASSERT(dynamic_cast<Sprite*>(child) != nullptr, "CCSpriteBatchNode only supports Sprites as children"); Sprite *sprite = static_cast<Sprite*>(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();//重新调整容器大小, } _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(); //把新sprite的顶点信息放到textureAtlas,供绘制使用,此时的顶点坐标还是在父亲中的坐标,不是世界坐标 _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。 } }
遍历方法visit
// override visit // don't call visit on it's children 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<Sprite*>(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<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。 //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)++; } } }
swap把新排序的顶点数据更新到 quads中,用于为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); std::swap( *oldIt, *newIt ); }
重新的元素draw方法,并不是真的绘制,而是把绘制命令放到了_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) { //更新孩子的坐标,递归 child->updateTransform(); } _batchCommand.init( _globalZOrder, getGLProgram(), _blendFunc, _textureAtlas, transform); renderer->addCommand(&_batchCommand); }
void Sprite::updateTransform(void) { CCASSERT(_batchNode, "updateTransform is only valid when Sprite is being rendered using an SpriteBatchNode"); // recalculate matrix only if it is dirty if( isDirty() ) { // If it is not visible, or one of its ancestors is not visible, then do nothing: if( !_visible || ( _parent && _parent != _batchNode && static_cast<Sprite*>(_parent)->_shouldBeHidden) ) { _quad.br.vertices = _quad.tl.vertices = _quad.tr.vertices = _quad.bl.vertices = Vec3(0,0,0); _shouldBeHidden = true; } else { _shouldBeHidden = false; //如果直接事batchNode的第一子元素,那么直接getNodeToParentTransform if( ! _parent || _parent == _batchNode ) { //得到子坐标转换父亲坐标的矩阵 _transformToBatch = getNodeToParentTransform(); } else //如果是batchNode的孙子或者往后,那么要下面这么做,孙子乘以transformToBatch得到在batch中的坐标 { CCASSERT( dynamic_cast<Sprite*>(_parent), "Logic error in Sprite. Parent must be a Sprite"); const Mat4 &nodeToParent = getNodeToParentTransform(); Mat4 &parentTransform = static_cast<Sprite*>(_parent)->_transformToBatch; _transformToBatch = parentTransform * nodeToParent; } // // calculate the Quad based on the Affine Matrix // Size &size = _rect.size; float x1 = _offsetPosition.x; float y1 = _offsetPosition.y; float x2 = x1 + size.width; float y2 = y1 + size.height; /* _transformToBatch 为 本地坐标转父亲坐标的旋转矩阵。这个矩阵乘以sprite的 [x,y,z,1],得到sprite在Batch中的的坐标 */ float x = _transformToBatch.m[12]; float y = _transformToBatch.m[13]; float cr = _transformToBatch.m[0]; float sr = _transformToBatch.m[1]; float cr2 = _transformToBatch.m[5]; float sr2 = -_transformToBatch.m[4]; float ax = x1 * cr - y1 * sr2 + x; float ay = x1 * sr + y1 * cr2 + y; float bx = x2 * cr - y1 * sr2 + x; float by = x2 * sr + y1 * cr2 + y; float cx = x2 * cr - y2 * sr2 + x; float cy = x2 * sr + y2 * cr2 + y; float dx = x1 * cr - y2 * sr2 + x; float dy = x1 * sr + y2 * cr2 + y; _quad.bl.vertices = Vec3( RENDER_IN_SUBPIXEL(ax), RENDER_IN_SUBPIXEL(ay), _positionZ ); _quad.br.vertices = Vec3( RENDER_IN_SUBPIXEL(bx), RENDER_IN_SUBPIXEL(by), _positionZ ); _quad.tl.vertices = Vec3( RENDER_IN_SUBPIXEL(dx), RENDER_IN_SUBPIXEL(dy), _positionZ ); _quad.tr.vertices = Vec3( RENDER_IN_SUBPIXEL(cx), RENDER_IN_SUBPIXEL(cy), _positionZ ); } // MARMALADE CHANGE: ADDED CHECK FOR nullptr, TO PERMIT SPRITES WITH NO BATCH NODE / TEXTURE ATLAS //把更新的四边形保存到 内存中,此时四边形的四个顶点坐标为在Batch中的坐标,不是世界坐标 if (_textureAtlas) { _textureAtlas->updateQuad(&_quad, _atlasIndex); } _recursiveDirty = false; setDirty(false); } // MARMALADE CHANGED // recursively iterate over children /* if( _hasChildren ) { // MARMALADE: CHANGED TO USE Node* // NOTE THAT WE HAVE ALSO DEFINED virtual Node::updateTransform() arrayMakeObjectsPerformSelector(_children, updateTransform, Sprite*); }*/ //递归更新 Node::updateTransform(); }
3 render中执行command
void Renderer::visitRenderQueue(const RenderQueue& queue) { ssize_t size = queue.size(); for (ssize_t index = 0; index < size; ++index) { auto command = queue[index]; auto commandType = command->getType(); if(RenderCommand::Type::BATCH_COMMAND == commandType) { flush(); auto cmd = static_cast<BatchCommand*>(command); cmd->execute(); } else { CCLOGERROR("Unknown commands in renderQueue"); } } }
4 RenderCommand 内执行execute
void BatchCommand::execute() { // Set material _shader->use(); _shader->setUniformsForBuiltins(_mv);//_mv为模型矩阵,本地坐标乘以mv,得到世界坐标 glActiveTexture(GL_TEXTURE0 + 0);//0号纹理单元 //将一个纹理对象设置为当前纹理对象,同时她会指派给当前激活了的纹理单元 GL::bindTexture2D(_textureID); GL::blendFunc(_blendType.src, _blendType.dst);//颜色混合 if ( _textureAtlas->getTexture()->getHasAlphaTexture()) { auto loc =glGetUniformLocation(_shader->getProgram(), "CC_Texture1"); glUniform1i(loc,1); auto alpha=Director::getInstance()->getTextureCache()->addImage(_textureAtlas->getTexture()->getAlphaTexture()); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, alpha->getName()); } auto locHasAlpha=glGetUniformLocation(_shader->getProgram(),"u_hasAlpha"); glUniform1f(locHasAlpha, _textureAtlas->getTexture()->getHasAlphaTexture()?1.0f:0.0f); // Draw,开始opengl画 _textureAtlas->drawQuads(); }
5 textureAtlas执行drawQuads方法
void TextureAtlas::drawQuads() { this->drawNumberOfQuads(_totalQuads, 0); } void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads) { CCASSERT(numberOfQuads>=0, "numberOfQuads must be >= 0"); this->drawNumberOfQuads(numberOfQuads, 0); } //渲染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 // // XXX: 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 /**************************** _quads中的顶点坐标为所有sprite在batch中的坐标,再顶点着色器中在转换为世界坐标 ****************************/ glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (numberOfQuads-start), nullptr, GL_DYNAMIC_DRAW); void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); memcpy(buf, _quads, sizeof(_quads[0])* (numberOfQuads-start)); 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])) ); #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]); // XXX: update is done in draw... perhaps it should be done in a timer if (_dirty) { glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * numberOfQuads , &_quads[start] ); _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(); }
以上主要步骤完成了SpriteBatchNode中sprite的绘制