label的绘制是在onDraw完成的
void Label::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (!_shadowEnabled && (_currentLabelType == LabelType::BMFONT || _currentLabelType == LabelType::CHARMAP)) { } else { // 项目中使用的是TTF,label绘制走的是CustomCommand _customCommand.init(_globalZOrder, transform, flags); _customCommand.func = CC_CALLBACK_0(Label::onDraw, this, transform, transformUpdated); renderer->addCommand(&_customCommand); } } // 处理渲染命令时,碰到CustomCommand的逻辑 void Renderer::processRenderCommand(RenderCommand* command) { if(RenderCommand::Type::CUSTOM_COMMAND == commandType) { flush(); auto cmd = static_cast<CustomCommand*>(command); CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_CUSTOM_COMMAND"); cmd->execute();// 执行绑定的渲染函数: cmd.func() } } // 进行label的绘制 void Label::onDraw(const Mat4& transform, bool /*transformUpdated*/) { // 最终的绘制结果都会放在BatchNode里面 for (auto&& batchNode : _batchNodes) { // ↓ 借用了Atlas进行渲染 batchNode->getTextureAtlas()->drawQuads(); } } void TextureAtlas::drawQuads() { this->drawNumberOfQuads(_totalQuads, 0); } void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start) { // 完成实际的绘制 glDrawElements(GL_TRIANGLES, (GLsizei)numberOfQuads*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(_indices[0]))); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,numberOfQuads*6);// 告诉统计增加绘制批次 }
经过以上的分析,那如何知道label的绘制批次呢?也就是label要保存最后调用统计时的dcIndex
label.batchNode身上有BatchCommand,但是label没有使用这个BatchCommand,这个BatchCommand是给Sprite准备的
void Renderer::processRenderCommand(RenderCommand* command) { if(RenderCommand::Type::BATCH_COMMAND == commandType) { flush(); auto cmd = static_cast<BatchCommand*>(command); CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_COMMAND"); cmd->execute(); } } void BatchCommand::execute() { // Set material _shader->use(); _shader->setUniformsForBuiltins(_mv); GL::bindTexture2D(_textureID); GL::blendFunc(_blendType.src, _blendType.dst); // Draw 可以观察到最后还是使用Atlas进行了渲染 _textureAtlas->drawQuads(); }
既然都是借用了Atlas,那么这个dcIndex还是存在Atlas上比较合适,在执行Atlas->drawQuads()
的时候,更新dcIndex,然后统一从Atlas获取这个dcIndex就可以了。
所以最后label获取dcIndex的逻辑就变成这样子
// 我一直认为是基于RenderCommand的,所以架构是基于RenderCommand cocos2d::RenderCommand* Label::getRenderCommand() { SpriteBatchNode* batchNode = this->_batchNodes.front(); // 这样就能直接拿到laebl的dcIndex,但是很明显和架构不符 batchNode->getTextureAtlas()->drawCallIndex; }
很明显,我们需要将这个dcIndex放到label绑定的CustomCommand。
能直接想到的就是,如果要更新cmd.dcIndex
,只能从调用cmd.func
的地方下手,2个思路:
// 方式1:在执行渲染回调的时候`cmd.func()`,通过返回值更新cmd.dcIndx // 这种方式能直观的看到dcIndex的修改逻辑,但是改动func会带来一些未知问题 cmd.dcIndex = cmd.func(); // 方式2:传递指针,让fuc内部修改cmd.dcIndex // 因为fuc可能实现差异很大,所以需要func内部自己处理dcIndex的赋值 cmd.func(cmd);
这里我采用第二种方式,仔细分析后,我发现
void Label::onDraw(const Mat4& transform, bool /*transformUpdated*/) { // 在执行这个Render的时候,很明显就是_customCommand导致的 // .... 渲染逻辑 // 在最后,修改_customCommand的dcIndex即可 if (_batchNodes.size() > 0) { unsigned int dcIndex = _batchNodes.front()->getTextureAtlas()->drawCallIndex; this->_customCommand.setDrawCallIndex(dcIndex); } } cocos2d::RenderCommand* Label::getRenderCommand() { if (!_shadowEnabled && (_currentLabelType == LabelType::BMFONT || _currentLabelType == LabelType::CHARMAP)) { return &(this->_quadCommand); } else { unsigned int size = this->_batchNodes.size(); if (size == 0) { return nullptr; } else { return &(this->_customCommand); // 在draw的时候已经正确赋值dcIndex了 } } return Node::getRenderCommand(); }
至此就完美拿到ttf的label绘制批次了
std::function绑定的小细节
// ↓可变参数绑定的方式 #define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__) // _customCommand.func的函数是无参的: std::function<void()> func; _customCommand.func = CC_CALLBACK_0(Label::onDraw, this, transform, transformUpdated); // 但是绑定的onDraw函数是有参数的,原因是可变参数绑定 void Label::onDraw(const Mat4& transform, bool /*transformUpdated*/){} // 可以看到其他的onDraw函数参数类型不同 void DrawNode::onDraw(const Mat4 &transform, uint32_t /*flags*/)
std::bind
是 C++ 标准库中的函数对象绑定器,用于将函数和其参数绑定为一个可调用的对象。
在给定的代码片段中,std::bind
函数被使用,它接受一个函数指针或可调用对象(如函数、函数对象、成员函数等),并将其与一组参数绑定在一起。这些参数可以是直接传递的值,也可以是占位符(_1
、_2
、_3
等)。
&__selector__
是要绑定的函数或可调用对象的名称(或地址)。__target__
是绑定的目标对象。##__VA_ARGS__
是可变参数,可以是一系列要绑定的参数。
这样的绑定操作旨在创建一个可调用的对象,使得在调用该对象时,被绑定的函数会以预先指定的目标对象和参数进行调用。这种方式在一些场景中非常有用,例如回调函数、事件处理等。