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__是可变参数,可以是一系列要绑定的参数。
这样的绑定操作旨在创建一个可调用的对象,使得在调用该对象时,被绑定的函数会以预先指定的目标对象和参数进行调用。这种方式在一些场景中非常有用,例如回调函数、事件处理等。