Cocos2dx踩坑-spine内存泄露+引擎源码分析

问题原因

当存在spine动画时,调用cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()可能会引起内存泄漏。

源码分析

当创建一个Spine动画并把它在场景中展示出来时,每一帧都会调用SkeletonRenderer::draw()
在draw()中会获取SkeletonBatch和SkeletonTwoColorBatch的单例,并调用它们的addCommand()、allocateVertices()、allocateIndices()

void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t transformFlags) {
    ......
    ......
 
    const float* worldCoordPtr = worldCoords;
    SkeletonBatch* batch = SkeletonBatch::getInstance();
    SkeletonTwoColorBatch* twoColorBatch = SkeletonTwoColorBatch::getInstance();
    const bool hasSingleTint = (isTwoColorTint() == false);
 
    ......
    ......
 
    for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) {
       ......
        ......
 
        if (hasSingleTint) {
            if (_clipper->isClipping()) {
                ......
                ......
#if COCOS2D_VERSION < 0x00040000
                ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
#else
                ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
#endif
                ......
                ......
            } else {
                // Not clipping.
#if COCOS2D_VERSION < 0x00040000
                ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
#else
                ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
#endif
 
              ......
                ......
            }
        } else {
               ......
                ...... 
        }
        _clipper->clipEnd(*slot);
    }
    ......
    ...... 
}

我们以SkeletonBatch的代码进行分析(SkeletonTwoColorBatch情况相同):

  • 在SkeletonBatch的构造函数中,先初始化了10000个command放到_commandsPool中。然后监听eventDispatcher发送EVENT_AFTER_DRAW_RESET_POSITIO(director_after_draw)事件,去执行update(),对_nextFreeCommand、_numVertices、_indices进行重置。
  • 在addCommand()中会调用nextFreeCommand(),在nextFreeCommand()中,如果_nextFreeCommand>=_commandsPool.size(),对_commandsPool进行2倍扩容, _nextFreeCommand加1。
  • 同理在allocateVertices()、allocateIndices()中如果_numVertices、_indices过大也会对_vertices和_indices进行2倍扩容,并增加_numVertices、_indices。

spine动画的正常逻辑:是在每一帧draw的时候处理当前帧的批处理命令(这个过程中会增加_nextFreeCommand、_numVertices、_indices),然后再通过监听director_after_draw事件重置_nextFreeCommand、_numVertices、_indices。

但是cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()方法会清空所有监听director_after_draw事件的方法,导致SkeletonBatch无法重置_nextFreeCommand、_numVertices、_indices。随着每一帧draw()的调用,_commandsPool、_vertices、_indicesd的容量会越来越大。导致内存泄漏。

USING_NS_AX;
#define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
using std::max;
#define INITIAL_SIZE (10000)
......
......
 
SkeletonBatch::SkeletonBatch () {
 
    auto program = backend::Program::getBuiltinProgram(backend::ProgramType::POSITION_TEXTURE_COLOR);
    _programState = new backend::ProgramState(program); // new default program state
    for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
        _commandsPool.push_back(newCommand());
    }
    reset();
    // callback after drawing is finished so we can clear out the batch state
    // for the next frame
    Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom) {
        this->update(0);
        });;
}
 
SkeletonBatch::~SkeletonBatch () {
    Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
 
    for (unsigned int i = 0; i < _commandsPool.size(); i++) {
        AX_SAFE_RELEASE(_commandsPool[i]->getPipelineDescriptor().programState);
        delete _commandsPool[i];
        _commandsPool[i] = nullptr;
    }
    AX_SAFE_RELEASE(_programState);
}
 
void SkeletonBatch::update (float delta) {
    reset();
}
 
ax::TrianglesCommand* SkeletonBatch::addCommand(ax::Renderer* renderer, float globalOrder, ax::Texture2D* texture, backend::ProgramState* programState, ax::BlendFunc blendType, const ax::TrianglesCommand::Triangles& triangles, const ax::Mat4& mv, uint32_t flags) {
    SkeletonCommand* command = nextFreeCommand();
    const ax::Mat4& projectionMat = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);   
 
    if (programState == nullptr)
        programState = _programState;
 
    AXASSERT(programState, "programState should not be null");
 
    auto pipelinePS = updateCommandPipelinePS(command, programState);
     
    pipelinePS->setUniform(command->_locMVP, projectionMat.m, sizeof(projectionMat.m));
    pipelinePS->setTexture(command->_locTexture, 0, texture->getBackendTexture());
 
    command->init(globalOrder, texture, blendType, triangles, mv, flags);
    renderer->addCommand(command);
    return command;
}
 
void SkeletonBatch::reset() {
    _nextFreeCommand = 0;
    _numVertices = 0;
    _indices.setSize(0, 0);
}
 
SkeletonCommand* SkeletonBatch::nextFreeCommand() {
    if (_commandsPool.size() <= _nextFreeCommand) {
        unsigned int newSize = _commandsPool.size() * 2 + 1;
        for (int i = _commandsPool.size(); i < newSize; i++) {
            _commandsPool.push_back(newCommand());
        }
    }
    auto* command = _commandsPool[_nextFreeCommand++];
    return command;
}
 
SkeletonCommand* SkeletonBatch::newCommand() {
    auto* command = new SkeletonCommand();
    return command;
}

建议

  • 尽量不要同时使用Spine动画和cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()
  • 调用cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()后,恢复SkeletonBatch和SkeletonTwoColorBatch监听director_after_draw事件。
  • 自己实现一个清除监听的方法,对Spine做特殊处理,不要清除spine的监听director_after_draw事件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值