好记性不如铅笔头

C && C++, cocos2dx, 编程

cocos2dx2.2.x、3.0版本绘制流程

cocos2dx 3.0版本出来之后,可以发现绘制流程与cocos2dx 2.2.x版本相比,改动很大。这里笔记下两个版本的不同。

CONTENTS

cocos2dx 2.2.x版本绘制流程

首先打开main.cpp:

int APIENTRY _tWinMain(。。。。。)
{
。。。。。
    AppDelegate app;
。。。。。
    return CCApplication::sharedApplication()->run();
}

然后打开AppDelegate文件:

class  AppDelegate : private cocos2d::CCApplication
{
public:
    AppDelegate();
    virtual ~AppDelegate();
    virtual bool applicationDidFinishLaunching();
    virtual void applicationDidEnterBackground();
    virtual void applicationWillEnterForeground();
}

可以看到AppDelegate继承自CCApplication,并且重写了3个虚函数。AppDelegate初始化时,会调用到CCApplication构造函数:

// sharedApplication pointer
CCApplication * CCApplication::sm_pSharedApplication = 0;

CCApplication::CCApplication()
: m_hInstance(NULL)
, m_hAccelTable(NULL)
{
    m_hInstance    = GetModuleHandle(NULL);
    m_nAnimationInterval.QuadPart = 0;
    CC_ASSERT(! sm_pSharedApplication);
    sm_pSharedApplication = this;
}

这里就知道了sm_pSharedApplication的指针为AppDelegate的实例。那么根据代码

CCApplication* CCApplication::sharedApplication()
{
    CC_ASSERT(sm_pSharedApplication);
    return sm_pSharedApplication;
}
。。。。。
int CCApplication::run()
{
。。。。。
    if (!applicationDidFinishLaunching())
    {
        return 0;
    }
。。。。。
    while (1)
    {
			CCDirector::sharedDirector()->mainLoop();
。。。。。
    }

    return (int) msg.wParam;
}

可知,程序初始化时会调用AppDelegate的applicationDidFinishLaunching方法,然后循环调用mainLoop。

分析mainLoop代码:

CCDirector* CCDirector::sharedDirector(void)
{
    if (!s_SharedDirector)
    {
        s_SharedDirector = new CCDisplayLinkDirector();
        s_SharedDirector->init();
    }

    return s_SharedDirector;
}
void CCDisplayLinkDirector::mainLoop(void)
{
。。。。。
	drawScene();
。。。。。
}

可知,mainLoop其实是调用了drawScene方法。

void CCDirector::drawScene(void)
{
。。。。。
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
。。。。。
    kmGLPushMatrix();

    // draw the scene
    if (m_pRunningScene)
    {
        m_pRunningScene->visit();
    }
。。。。。
}

可以看到,drawScene方法实际上就是cocos2dx的绘图流程主入口,每次循环中,都是先glClear,然后调用场景的viist方法进行绘制场景。

class CC_DLL CCScene : public CCNode
void CCNode::visit()
{
    // quick return if not visible. children won't be drawn.
    if (!m_bVisible)
    {
        return;
    }
    kmGLPushMatrix();
。。。。。
    this->transform();

    CCNode* pNode = NULL;
    unsigned int i = 0;

    if(m_pChildren && m_pChildren->count() > 0)
    {
        sortAllChildren();
        // draw children zOrder < 0
        ccArray *arrayData = m_pChildren->data;
        for( ; i < arrayData->num; i++ )
        {
            pNode = (CCNode*) arrayData->arr[i];

            if ( pNode && pNode->m_nZOrder < 0 ) 
            {
                pNode->visit();
            }
            else
            {
                break;
            }
        }
        // self draw
        this->draw();

        for( ; i < arrayData->num; i++ )
        {
            pNode = (CCNode*) arrayData->arr[i];
            if (pNode)
            {
                pNode->visit();
            }
        }        
    }
    else
    {
        this->draw();
    }
。。。。。
    kmGLPopMatrix();
}

分析下CCNode的visit方法,可以发现它的流程:
如果Node没有子节点,那么直接调用draw方法,
如果Node有子节点,那么就根据子节点的Zorder进行排序,先找到子节点中Zorder小于Node本身的这些子节点,调用它们的visit方法,再调用Node本身的draw方法,在调用其余子节点的visit方法,这样就可以保证Zorder下的正确的层次关系。而且这里子节点调用的是visit方法,如果这个子节点还有子节点,就会一层一层的递归调用,直到子节点下没有节点了,直接调用draw方法。
由于CCSprite,CCLayer,CCScene都是继承自CCNode,而且重写了draw方法,那么通过这样的树形设计和递归调用,就可以使场景中每个节点(Sprite,Layer等)的draw方法都被调用而且是按Zorder的顺序调用的。

我们这里再看一下CCSprite的draw方法:

void CCSprite::draw(void)
{
。。。。。
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
。。。。。
}

可以看到,这里CCSprite的draw方法中直接把自己绘制出来了。
cocos2dx 2.2.x版本基本上都这个绘制流程。这个绘制流程有个好处,就是便于理解,每个节点都有自己的绘制方法,而且会在这个绘制方法中把自己绘制出来。但是有两个问题:
1 opengl绘制方法和游戏业务方法混在一块,opengl的代码遍布于整个cocos2dx工程,不好管理,代码层次较乱。
2 性能较低,由于各个节点都只绘制自己,无法得到其他节点的绘制情况。在有遮盖的情况下,绘制效率较低。比如某个节点A先绘制,然后第二个节点B绘制的时候覆盖掉了节点A,从效率上将,由于节点A被覆盖,实际上是可以不绘制的(绘制了也会被B遮住)。

cocos2dx 3.0版本绘制流程

cocos2dx 3.0版本和2.2.x版本的初始化流程差不多,最后都会调到Director的drawScene方法:

void Director::drawScene()
{
。。。。。
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
。。。。。
    kmGLPushMatrix();

    // global identity matrix is needed... come on kazmath!
    kmMat4 identity;
    kmMat4Identity(&identity);

    // draw the scene if (_runningScene)
    {
        _runningScene->visit(_renderer, identity, false);
        _eventDispatcher->dispatchEvent(_eventAfterVisit);
    }
。。。。。
    _renderer->render();
    _eventDispatcher->dispatchEvent(_eventAfterDraw);
。。。。。
}

这里看下3.0版本的CCNode的visit方法:

void Node::visit(Renderer* renderer, const kmMat4 &parentTransform, bool parentTransformUpdated)
{
    // quick return if not visible. children won't be drawn.
    if (!_visible)
    {
        return;
    }

    bool dirty = _transformUpdated || parentTransformUpdated;
    if(dirty)
        _modelViewTransform = this->transform(parentTransform);
    _transformUpdated = false;


    // IMPORTANT:
    // To ease the migration to v3.0, we still support the kmGL stack,
    // but it is deprecated and your code should not rely on it
    kmGLPushMatrix();
    kmGLLoadMatrix(&_modelViewTransform);

    int i = 0;

    if(!_children.empty())
    {
        sortAllChildren();
        // draw children zOrder < 0
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);

            if ( node && node->_localZOrder < 0 )
                node->visit(renderer, _modelViewTransform, dirty);
            else
                break;
        }
        // self draw
        this->draw(renderer, _modelViewTransform, dirty);

        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
            (*it)->visit(renderer, _modelViewTransform, dirty);
    }
    else
    {
        this->draw(renderer, _modelViewTransform, dirty);
    }

    // reset for next frame
    _orderOfArrival = 0;
 
    kmGLPopMatrix();
}

和2.2.x的类似,也是遍历子节点,调用draw方法。那么看下draw方法:

void Node::draw(Renderer* renderer, const kmMat4 &transform, bool transformUpdated)
{
}

空函数,没有实现,我们在看下CCSprite的draw方法:

void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
    // Don't do calculate the culling if the transform was not updated
    _insideBounds = transformUpdated ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
    {
        _quadCommand.init(_globalZOrder, _texture->getName(), _shaderProgram, _blendFunc, &_quad, 1, transform);
        renderer->addCommand(&_quadCommand);
        。。。。。
    }
}

这里就有区别了,可以发现,3.0版本的Sprite的draw方法中没有直接调用opengl绘制,而是把要绘制的内容和信息添加到了renderer中。那么通过树形设计和递归调用,场景中每个节点(Sprite,Layer等)的draw方法都会把自己要绘制的信息添加到renderer中。然后回到drawScene方法,可以发现,当场景的visit方法调用完并返回后,程序会调用_renderer的render()方法,在此方法中进行统一绘制。
分析到此,就可以初步的看出3.0版本的绘制流程了:先把要绘制的信息和数据收集起来,然后统一绘制。由于篇幅关系,这里就不继续分析CCRenderer类的方法了。

发表评论

20 + 9 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据