Cocos2dx源码赏析(2)之渲染必发365bifa0000

by admin on 2019年2月20日

Cocos2dx源码赏析(2)之渲染

这篇,继续从源码的角度来跟踪下Cocos2dx引擎的渲染进程,以此来梳理下Cocos2dx引擎是怎么着将灵活等因素显示在屏幕上的。

从上一篇对Cocos2dx运行流程的梳理中可见,Cocos2dx凭借通过各平台的入口运维发动机,并在循环中调用Director::mainLoop方法来维持引擎的各类逻辑:

void Director::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();

        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

void Director::end()
{
    _purgeDirectorInNextLoop = true;
}

void Director::restart()
{
    _restartDirectorInNextLoop = true;
}

void Director::stopAnimation()
{
    _invalid = true;
}

当调用了Director::end()方法时,_purgeDirectorInNextLoop变量才会被置为true,并执行了purgeDirector()方法:

void Director::purgeDirector()
{
    reset();

    CHECK_GL_ERROR_DEBUG();

    // OpenGL view
    if (_openGLView)
    {
        _openGLView->end();
        _openGLView = nullptr;
    }

    // delete Director
    release();
}

能够观察,那里举行了有些重置和清理工作。即在须要收尾游戏的时候,可以调用Director::end()方法,让引擎跳出主循环,执行关闭。

调用了Director::restart()方法时,_restartDirectorInNextLoop变量会被置为true,即会履行restartDirector()方法:

void Director::restartDirector()
{
    reset();

    // RenderState need to be reinitialized
    RenderState::initialize();

    // Texture cache need to be reinitialized
    initTextureCache();

    // Reschedule for action manager
    getScheduler()->scheduleUpdate(getActionManager(), Scheduler::PRIORITY_SYSTEM, false);

    // release the objects
    PoolManager::getInstance()->getCurrentPool()->clear();

    // Restart animation
    startAnimation();

    // Real restart in script level
#if CC_ENABLE_SCRIPT_BINDING
    ScriptEvent scriptEvent(kRestartGame, nullptr);
    ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent);
#endif
}

可以看到,在restartDirector方法中,先进行了重置reset方法,然后又跟着把渲染状态、纹理缓存、定时器、内存管理、动画等又重新最先化了。以此来促成游戏重启的方案。

_invalid变量暗许是true,刚初阶在Director::init中会被置为false,在调用Director::stopAnimation()时,会将_invalid置为true,此时不满足条件,即不会调用drawScene()绘制场景的法门,当然在调用Director::startAnimation()又会将_invalid置为false,因此能够明白,当_invalid置为true时,引擎在做空循环。

上面,才总算真正进入正题,即当_invalid为false时,会调用drawScene方法来绘制场景,设置定时器,动画,事件循环等一多级处理:

void Director::drawScene()
{
    // calculate "global" dt
    calculateDeltaTime();

    if (_openGLView)
    {
        _openGLView->pollEvents();
    }

    //tick before glClear: issue #533
    if (! _paused)
    {
        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }

    _renderer->clear();
    experimental::FrameBuffer::clearAllFBOs();
    /* to avoid flickr, nextScene MUST be here: after tick and before draw.
     * FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9
     */
    if (_nextScene)
    {
        setNextScene();
    }

    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    if (_runningScene)
    {
#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)
        _runningScene->stepPhysicsAndNavigation(_deltaTime);
#endif
        //clear draw stats
        _renderer->clearDrawStats();

        //render the scene
        _openGLView->renderScene(_runningScene, _renderer);

        _eventDispatcher->dispatchEvent(_eventAfterVisit);
    }

    // draw the notifications node
    if (_notificationNode)
    {
        _notificationNode->visit(_renderer, Mat4::IDENTITY, 0);
    }

    if (_displayStats)
    {
        showStats();
    }
    _renderer->render();

    _eventDispatcher->dispatchEvent(_eventAfterDraw);

    popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    _totalFrames++;

    // swap buffers
    if (_openGLView)
    {
        _openGLView->swapBuffers();
    }

    if (_displayStats)
    {
        calculateMPF();
    }
}

首先在drawScene方法中,会先调用calculateDeltaTime方法来总计每帧的小时距离_deltaTime,即每帧执行一名目繁多逻辑操作所消费的时日。

收取里判断了_openGLView,该目的是用来将OpenGL绘制的内容展今后差别平台对应的视图上,那里差距的平台有两样的是完结。而_openGLView的赋值是在调用了Director::setOpenGLView方法里进行的,而setOpenGLView方法的调用,大家是在AppDelegate::applicationDidFinishLaunching()方法中调用的。所以,这里_openGLView符合规律情状下是不会为空的。那么,也就会履行_openGLView->poll伊夫nts()方法,那么些法子是个空已毕,只在特定的阳台才做相应的处理。一般会在该方式中,检查有没触发什么风云(键盘输入、鼠标移动等)。

再跟着有个_paused的判断,而_paused为置为true,即不满意条件是在调用了Director::pause方法中装置的,那么不满足条件时,就不会进行这里的代码:

    if (! _paused)
    {
        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }

相当于当调用了Director::pause的主意,然后进入主循环,可是不会响应相应的风云调度和定时器的革新处理。

此起彼伏往下执行,如下代码:

_renderer->clear();
experimental::FrameBuffer::clearAllFBOs();

那里,紧借使在绘制前,执行相应的清理工作(例如:清除颜色缓冲区和纵深缓冲区,清除帧缓冲对象等)。

下一场,就举办那行代码了:

if (_nextScene)
{
    setNextScene();
}

追踪一下,可以找到,在调用了Director的replaceScene、pushScene或popScene等格局时,会给_nextScene赋值,那一个措施的成效分别是:
replaceScene:将要执行的景观压入场景栈中,并替换当前的现象,_nextScene指向要实践的景况。
pushScene:将要执行的光景压入场景栈中,并将_nextScene指向要实施的场所。
popScene:在场景栈中弹出近期意况,并将_nextScene指向上一个的气象。

上述这八个措施都以在下一帧绘制生效。在setNextScene会执行一些地方的场所切换,并将下三个要履行的场景内定为当前运作的景色。

延续,再就执行下边的代码:

pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

if (_runningScene)
{
#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)
    _runningScene->stepPhysicsAndNavigation(_deltaTime);
#endif
    //clear draw stats
    _renderer->clearDrawStats();

    //render the scene
    _openGLView->renderScene(_runningScene, _renderer);

    _eventDispatcher->dispatchEvent(_eventAfterVisit);
}

pushMatrix会将模型视图的矩阵压入相应的栈中。而相应的栈有存放模型视图矩阵的栈,投影矩阵的栈,纹理矩阵的栈。接下来,首要看renderScene方法的调用。

void GLView::renderScene(Scene* scene, Renderer* renderer)
{
    CCASSERT(scene, "Invalid Scene");
    CCASSERT(renderer, "Invalid Renderer");

    if (_vrImpl)
    {
        _vrImpl->render(scene, renderer);
    }
    else
    {
        scene->render(renderer, Mat4::IDENTITY, nullptr);
    }
}

这里,_vrImpl是关于V凯雷德的贯彻,那里先不尊敬。然后,就调用到了scene的render方法:

void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount)
{
    auto director = Director::getInstance();
    Camera* defaultCamera = nullptr;
    const auto& transform = getNodeToParentTransform();

    for (const auto& camera : getCameras())
    {
        if (!camera->isVisible())
            continue;

        Camera::_visitingCamera = camera;
        if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT)
        {
            defaultCamera = Camera::_visitingCamera;
        }

        // There are two ways to modify the "default camera" with the eye Transform:
        // a) modify the "nodeToParentTransform" matrix
        // b) modify the "additional transform" matrix
        // both alternatives are correct, if the user manually modifies the camera with a camera->setPosition()
        // then the "nodeToParent transform" will be lost.
        // And it is important that the change is "permanent", because the matrix might be used for calculate
        // culling and other stuff.
        for (unsigned int i = 0; i < multiViewCount; ++i) {
            if (eyeProjections)
                camera->setAdditionalProjection(eyeProjections[i] * camera->getProjectionMatrix().getInversed());
            if (eyeTransforms)
                camera->setAdditionalTransform(eyeTransforms[i].getInversed());
            director->pushProjectionMatrix(i);
            director->loadProjectionMatrix(Camera::_visitingCamera->getViewProjectionMatrix(), i);
        }

        camera->apply();
        //clear background with max depth
        camera->clearBackground();
        //visit the scene
        visit(renderer, transform, 0);
#if CC_USE_NAVMESH
        if (_navMesh && _navMeshDebugCamera == camera)
        {
            _navMesh->debugDraw(renderer);
        }
#endif

        renderer->render();
        camera->restore();

        for (unsigned int i = 0; i < multiViewCount; ++i)
            director->popProjectionMatrix(i);

        // we shouldn't restore the transform matrix since it could be used
        // from "update" or other parts of the game to calculate culling or something else.
//        camera->setNodeToParentTransform(eyeCopy);
    }

#if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION
    if (_physics3DWorld && _physics3DWorld->isDebugDrawEnabled())
    {
        Camera *physics3dDebugCamera = _physics3dDebugCamera != nullptr ? _physics3dDebugCamera: defaultCamera;

        for (unsigned int i = 0; i < multiViewCount; ++i) {
            if (eyeProjections)
                physics3dDebugCamera->setAdditionalProjection(eyeProjections[i] * physics3dDebugCamera->getProjectionMatrix().getInversed());
            if (eyeTransforms)
                physics3dDebugCamera->setAdditionalTransform(eyeTransforms[i].getInversed());
            director->pushProjectionMatrix(i);
            director->loadProjectionMatrix(physics3dDebugCamera->getViewProjectionMatrix(), i);
        }

        physics3dDebugCamera->apply();
        physics3dDebugCamera->clearBackground();

        _physics3DWorld->debugDraw(renderer);
        renderer->render();

        physics3dDebugCamera->restore();

        for (unsigned int i = 0; i < multiViewCount; ++i)
            director->popProjectionMatrix(i);
    }
#endif

    Camera::_visitingCamera = nullptr;
//    experimental::FrameBuffer::applyDefaultFBO();
}

在那么些render方法中,紧要关怀五个措施的调用,即下边那两行代码:

visit(renderer, transform, 0);
renderer->render();

那里的visit会调用到父类Node节点相应的visit方法:

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

    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->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    _director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);

    bool visibleByCamera = isVisitableByVisitingCamera();

    int i = 0;

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

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

        for(auto it=_children.cbegin()+i, itCend = _children.cend(); it != itCend; ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    }
    else if (visibleByCamera)
    {
        this->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
    // reset for next frame
    // _orderOfArrival = 0;
}

该方法首先会对日前节点下的子节点举办遍历并排序,那里遍历会遍历整个Node节点树,然后在调用自个儿的绘图方法draw。例如,精灵百事可乐会调用精灵自个儿的draw方法:

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    if (_texture == nullptr)
    {
        return;
    }

#if CC_USE_CULLING
    // Don't calculate the culling if the transform was not updated
    auto visitingCamera = Camera::getVisitingCamera();
    auto defaultCamera = Camera::getDefaultCamera();
    if (visitingCamera == defaultCamera) {
        _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
    }
    else
    {
        // XXX: this always return true since
        _insideBounds = renderer->checkVisibility(transform, _contentSize);
    }

    if(_insideBounds)
#endif
    {
        _trianglesCommand.init(_globalZOrder,
                               _texture,
                               getGLProgramState(),
                               _blendFunc,
                               _polyInfo.triangles,
                               transform,
                               flags);

        renderer->addCommand(&_trianglesCommand);

#if CC_SPRITE_DEBUG_DRAW
        _debugDrawNode->clear();
        auto count = _polyInfo.triangles.indexCount/3;
        auto indices = _polyInfo.triangles.indices;
        auto verts = _polyInfo.triangles.verts;
        for(ssize_t i = 0; i < count; i++)
        {
            //draw 3 lines
            Vec3 from =verts[indices[i*3]].vertices;
            Vec3 to = verts[indices[i*3+1]].vertices;
            _debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);

            from =verts[indices[i*3+1]].vertices;
            to = verts[indices[i*3+2]].vertices;
            _debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);

            from =verts[indices[i*3+2]].vertices;
            to = verts[indices[i*3]].vertices;
            _debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
        }
#endif //CC_SPRITE_DEBUG_DRAW
    }
}

在Coca Cola的draw方法中,并不直接绘制,而是给renderer发送壹个RenderCommand指令(那里是TrianglesCommand),renderer会将RenderCommand放入三个栈中,等Node节点成分都遍历完结,才实施RenderCommand指令。

依据目标版本的发动机已毕,就将绘制逻辑从Node节点树遍历中分离出来了。每回绘制就给renderer发送1个RenderCommand指令。

接下去看Renderer::render方法:

void Renderer::render()
{
    //Uncomment this once everything is rendered by new renderer
    //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //TODO: setup camera or MVP
    _isRendering = true;

    if (_glViewAssigned)
    {
        //Process render commands
        //1. Sort render commands based on ID
        for (auto &renderqueue : _renderGroups)
        {
            renderqueue.sort();
        }
        visitRenderQueue(_renderGroups[0]);
    }
    clean();
    _isRendering = false;
}

此间取出下标为0的渲染队列,然后,进一步通过visitRenderQueue来获取队列中的渲染指令Command:

void Renderer::visitRenderQueue(RenderQueue& queue)
{
    queue.saveRenderState();

    //
    //Process Global-Z < 0 Objects
    //
    const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);
    if (zNegQueue.size() > 0)
    {
        if(_isDepthTestFor2D)
        {
            glEnable(GL_DEPTH_TEST);
            glDepthMask(true);
            glEnable(GL_BLEND);
            RenderState::StateBlock::_defaultState->setDepthTest(true);
            RenderState::StateBlock::_defaultState->setDepthWrite(true);
            RenderState::StateBlock::_defaultState->setBlend(true);
        }
        else
        {
            glDisable(GL_DEPTH_TEST);
            glDepthMask(false);
            glEnable(GL_BLEND);
            RenderState::StateBlock::_defaultState->setDepthTest(false);
            RenderState::StateBlock::_defaultState->setDepthWrite(false);
            RenderState::StateBlock::_defaultState->setBlend(true);
        }
        glDisable(GL_CULL_FACE);
        RenderState::StateBlock::_defaultState->setCullFace(false);

        for (const auto& zNegNext : zNegQueue)
        {
            processRenderCommand(zNegNext);
        }
        flush();
    }

    //
    //Process Opaque Object
    //
    const auto& opaqueQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::OPAQUE_3D);
    if (opaqueQueue.size() > 0)
    {
        //Clear depth to achieve layered rendering
        glEnable(GL_DEPTH_TEST);
        glDepthMask(true);
        glDisable(GL_BLEND);
        glEnable(GL_CULL_FACE);
        RenderState::StateBlock::_defaultState->setDepthTest(true);
        RenderState::StateBlock::_defaultState->setDepthWrite(true);
        RenderState::StateBlock::_defaultState->setBlend(false);
        RenderState::StateBlock::_defaultState->setCullFace(true);

        for (const auto& opaqueNext : opaqueQueue)
        {
            processRenderCommand(opaqueNext);
        }
        flush();
    }

    //
    //Process 3D Transparent object
    //
    const auto& transQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::TRANSPARENT_3D);
    if (transQueue.size() > 0)
    {
        glEnable(GL_DEPTH_TEST);
        glDepthMask(false);
        glEnable(GL_BLEND);
        glEnable(GL_CULL_FACE);

        RenderState::StateBlock::_defaultState->setDepthTest(true);
        RenderState::StateBlock::_defaultState->setDepthWrite(false);
        RenderState::StateBlock::_defaultState->setBlend(true);
        RenderState::StateBlock::_defaultState->setCullFace(true);


        for (const auto& transNext : transQueue)
        {
            processRenderCommand(transNext);
        }
        flush();
    }

    //
    //Process Global-Z = 0 Queue
    //
    const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO);
    if (zZeroQueue.size() > 0)
    {
        if(_isDepthTestFor2D)
        {
            glEnable(GL_DEPTH_TEST);
            glDepthMask(true);
            glEnable(GL_BLEND);

            RenderState::StateBlock::_defaultState->setDepthTest(true);
            RenderState::StateBlock::_defaultState->setDepthWrite(true);
            RenderState::StateBlock::_defaultState->setBlend(true);
        }
        else
        {
            glDisable(GL_DEPTH_TEST);
            glDepthMask(false);
            glEnable(GL_BLEND);

            RenderState::StateBlock::_defaultState->setDepthTest(false);
            RenderState::StateBlock::_defaultState->setDepthWrite(false);
            RenderState::StateBlock::_defaultState->setBlend(true);
        }
        glDisable(GL_CULL_FACE);
        RenderState::StateBlock::_defaultState->setCullFace(false);

        for (const auto& zZeroNext : zZeroQueue)
        {
            processRenderCommand(zZeroNext);
        }
        flush();
    }

    //
    //Process Global-Z > 0 Queue
    //
    const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS);
    if (zPosQueue.size() > 0)
    {
        if(_isDepthTestFor2D)
        {
            glEnable(GL_DEPTH_TEST);
            glDepthMask(true);
            glEnable(GL_BLEND);

            RenderState::StateBlock::_defaultState->setDepthTest(true);
            RenderState::StateBlock::_defaultState->setDepthWrite(true);
            RenderState::StateBlock::_defaultState->setBlend(true);
        }
        else
        {
            glDisable(GL_DEPTH_TEST);
            glDepthMask(false);
            glEnable(GL_BLEND);

            RenderState::StateBlock::_defaultState->setDepthTest(false);
            RenderState::StateBlock::_defaultState->setDepthWrite(false);
            RenderState::StateBlock::_defaultState->setBlend(true);
        }
        glDisable(GL_CULL_FACE);
        RenderState::StateBlock::_defaultState->setCullFace(false);

        for (const auto& zPosNext : zPosQueue)
        {
            processRenderCommand(zPosNext);
        }
        flush();
    }

    queue.restoreRenderState();
}

下一场,取出队列中的Command,并举办processRenderCommand方法:

void Renderer::processRenderCommand(RenderCommand* command)
{
    auto commandType = command->getType();
    if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
    {
        // flush other queues
        flush3D();

        auto cmd = static_cast<TrianglesCommand*>(command);

        // flush own queue when buffer is full
        if(_filledVertex + cmd->getVertexCount() > VBO_SIZE || _filledIndex + cmd->getIndexCount() > INDEX_VBO_SIZE)
        {
            CCASSERT(cmd->getVertexCount()>= 0 && cmd->getVertexCount() < VBO_SIZE, "VBO for vertex is not big enough, please break the data down or use customized render command");
            CCASSERT(cmd->getIndexCount()>= 0 && cmd->getIndexCount() < INDEX_VBO_SIZE, "VBO for index is not big enough, please break the data down or use customized render command");
            drawBatchedTriangles();
        }

        // queue it
        _queuedTriangleCommands.push_back(cmd);
        _filledIndex += cmd->getIndexCount();
        _filledVertex += cmd->getVertexCount();
    }
    else if (RenderCommand::Type::MESH_COMMAND == commandType)
    {
        flush2D();
        auto cmd = static_cast<MeshCommand*>(command);

        if (cmd->isSkipBatching() || _lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())
        {
            flush3D();

            CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_MESH_COMMAND");

            if(cmd->isSkipBatching())
            {
                // XXX: execute() will call bind() and unbind()
                // but unbind() shouldn't be call if the next command is a MESH_COMMAND with Material.
                // Once most of cocos2d-x moves to Pass/StateBlock, only bind() should be used.
                cmd->execute();
            }
            else
            {
                cmd->preBatchDraw();
                cmd->batchDraw();
                _lastBatchedMeshCommand = cmd;
            }
        }
        else
        {
            CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_MESH_COMMAND");
            cmd->batchDraw();
        }
    }
    else if(RenderCommand::Type::GROUP_COMMAND == commandType)
    {
        flush();
        int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
        CCGL_DEBUG_PUSH_GROUP_MARKER("RENDERER_GROUP_COMMAND");
        visitRenderQueue(_renderGroups[renderQueueID]);
        CCGL_DEBUG_POP_GROUP_MARKER();
    }
    else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
    {
        flush();
        auto cmd = static_cast<CustomCommand*>(command);
        CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_CUSTOM_COMMAND");
        cmd->execute();
    }
    else if(RenderCommand::Type::BATCH_COMMAND == commandType)
    {
        flush();
        auto cmd = static_cast<BatchCommand*>(command);
        CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_COMMAND");
        cmd->execute();
    }
    else if(RenderCommand::Type::PRIMITIVE_COMMAND == commandType)
    {
        flush();
        auto cmd = static_cast<PrimitiveCommand*>(command);
        CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_PRIMITIVE_COMMAND");
        cmd->execute();
    }
    else
    {
        CCLOGERROR("Unknown commands in renderQueue");
    }
}

可以看到在processRenderCommand中就是各体系型的Command的施行和相应的处理了。而在Coca Cola的绘图发的是T大切诺基IANGLES_COMMAND类型的命令,所以,直接看那一个drawBatchedTriangles:

void Renderer::drawBatchedTriangles()
{
    if(_queuedTriangleCommands.empty())
        return;

    CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_TRIANGLES");

    _filledVertex = 0;
    _filledIndex = 0;

    /************** 1: Setup up vertices/indices *************/

    _triBatchesToDraw[0].offset = 0;
    _triBatchesToDraw[0].indicesToDraw = 0;
    _triBatchesToDraw[0].cmd = nullptr;

    int batchesTotal = 0;
    int prevMaterialID = -1;
    bool firstCommand = true;

    for(const auto& cmd : _queuedTriangleCommands)
    {
        auto currentMaterialID = cmd->getMaterialID();
        const bool batchable = !cmd->isSkipBatching();

        fillVerticesAndIndices(cmd);

        // in the same batch ?
        if (batchable && (prevMaterialID == currentMaterialID || firstCommand))
        {
            CC_ASSERT(firstCommand || _triBatchesToDraw[batchesTotal].cmd->getMaterialID() == cmd->getMaterialID() && "argh... error in logic");
            _triBatchesToDraw[batchesTotal].indicesToDraw += cmd->getIndexCount();
            _triBatchesToDraw[batchesTotal].cmd = cmd;
        }
        else
        {
            // is this the first one?
            if (!firstCommand) {
                batchesTotal++;
                _triBatchesToDraw[batchesTotal].offset = _triBatchesToDraw[batchesTotal-1].offset + _triBatchesToDraw[batchesTotal-1].indicesToDraw;
            }

            _triBatchesToDraw[batchesTotal].cmd = cmd;
            _triBatchesToDraw[batchesTotal].indicesToDraw = (int) cmd->getIndexCount();

            // is this a single batch ? Prevent creating a batch group then
            if (!batchable)
                currentMaterialID = -1;
        }

        // capacity full ?
        if (batchesTotal + 1 >= _triBatchesToDrawCapacity) {
            _triBatchesToDrawCapacity *= 1.4;
            _triBatchesToDraw = (TriBatchToDraw*) realloc(_triBatchesToDraw, sizeof(_triBatchesToDraw[0]) * _triBatchesToDrawCapacity);
        }

        prevMaterialID = currentMaterialID;
        firstCommand = false;
    }
    batchesTotal++;

    /************** 2: Copy vertices/indices to GL objects *************/
    auto conf = Configuration::getInstance();
    if (conf->supportsShareableVAO() && conf->supportsMapBuffer())
    {
        //Bind VAO
        GL::bindVAO(_buffersVAO);
        //Set VBO data
        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(_verts[0]) * _filledVertex, _verts, GL_STATIC_DRAW);

        // option 3: orphaning + glMapBuffer
        // FIXME: in order to work as fast as possible, it must "and the exact same size and usage hints it had before."
        //  source: https://www.opengl.org/wiki/Buffer_Object_Streaming#Explicit_multiple_buffering
        // so most probably we won't have any benefit of using it
        glBufferData(GL_ARRAY_BUFFER, sizeof(_verts[0]) * _filledVertex, nullptr, GL_STATIC_DRAW);
        void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        memcpy(buf, _verts, sizeof(_verts[0]) * _filledVertex);
        glUnmapBuffer(GL_ARRAY_BUFFER);

        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * _filledIndex, _indices, GL_STATIC_DRAW);
    }
    else
    {
        // Client Side Arrays
#define kQuadSize sizeof(_verts[0])
        glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);

        glBufferData(GL_ARRAY_BUFFER, sizeof(_verts[0]) * _filledVertex , _verts, GL_DYNAMIC_DRAW);

        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]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * _filledIndex, _indices, GL_STATIC_DRAW);
    }

    /************** 3: Draw *************/
    for (int i=0; i<batchesTotal; ++i)
    {
        CC_ASSERT(_triBatchesToDraw[i].cmd && "Invalid batch");
        _triBatchesToDraw[i].cmd->useMaterial();
        glDrawElements(GL_TRIANGLES, (GLsizei) _triBatchesToDraw[i].indicesToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (_triBatchesToDraw[i].offset*sizeof(_indices[0])) );
        _drawnBatches++;
        _drawnVertices += _triBatchesToDraw[i].indicesToDraw;
    }

    /************** 4: Cleanup *************/
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        //Unbind VAO
        GL::bindVAO(0);
    }
    else
    {
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    _queuedTriangleCommands.clear();
    _filledVertex = 0;
    _filledIndex = 0;
}

以此即是主要的绘图处理以及做相应的联合批次的拍卖。那里,就先写到这里,简单的说根本是些OpenGL
api的调用,可是,我对那么些还尚无深入的明亮,就不“误人子弟”,做过多的解析了,前边等进行过再来更新此篇,解释得更详细些。由此,该篇只是勉强对渲染的代码执行流程作了简便的剖析,谈不上尖锐驾驭。但起码经过翻阅代码,可以领略相应的拍卖是如何贯彻的。

技术交换QQ群:528655025
作者:AlphaGL
出处:http://www.cnblogs.com/alphagl/
版权全数,欢迎保留原文链接举行转发 🙂

有三回和自己对象的丈母娘一起用餐。

老太太问作者国际幼儿园的收款标准。作者说了,老太太眼睛瞪的如碗,连声问:“那几个价格,是要包吃包住,二个月才接两遍的长托吧?”

“半年接三遍?大妈,那些价位,是每周二上午不上课,外加七个月放三遍假的音频啊。”

十二月份进幼儿园,开学第叁天,送进去的小肉芽芽,保险哭的鼻青脸肿的归来。

哭几个礼拜,终于晚上送去的时候,可以不哭了。

十三月国庆节,全国放假了。

放假回来,一切重新开首。早晨送幼儿园,犹如人间鬼世界,哭声一片。

硬着心再放,锲而不舍几礼拜,终于又不哭了。

十七月初,又放假了。那是法兰西教育局的规定:期中假。

不看不清楚,世界真奇妙。大家是期中考试,他们是期中放假。

回到再送,孩子再哭。

煎熬一遍了,再固执的男女,哭的强度都在减弱。期中假回来,孩子们再哭也坚称不辍很久了。

子女们的心态,终于稳定下来。清晨送去的时候,可以坦然的让老师抱进去了。

一年中最快活的时刻来了。圣诞老人骑着麋鹿来送礼物,举天同庆,怎么能不放假?

圣诞节澳岁首,两到三周假。

接下来二月份开学。天冷,班上二个亲骨血病了,立马倒一片。四日多头的都要请病假。病假还没修完,寒假启幕了。

辛亏国际高校的寒假相比较短,也就多少个星期而已。

七月又放一次期中假。

三月复活节三个星期假。

二月一号,黄金周多少个星期假。

终于到了一月,除了七夕节那一天之外,整个七月份是绝非假的。

巴掌不用拍得太早,七月三十号,孩子们放暑假了。

十二月八月两全方位月的暑假。

身为实话,作者直接在可疑,教育系统设定暑假的意向,到底是为了孩子的健康,如故为了老师们的精神修养,亦大概根本为了促进国民经济?

总体暑假时期,孩子们被送去2个比另一个更贵的兴趣班只怕夏令营,赶场赶的比上课还忙。

全方位暑假日间,家长们出钱出力出时间,还不肯定讨孩子们的喜好。

冬季本来就热,一出唱下来,乱七八糟,心焦磨乱,鸡鸣狗跳,神不守舍。

10月一号放暑假,到了十10月十五号,问思迪最想干什么,大姑娘的答案是:“最想去上学。”

天天上午把子觅送到幼儿园,思迪跟自家回家。在自作者工作的时候,阿姨娘在家里团团撞墙。连快递小哥签个快递,都开心的13分。

本身看在眼里,真是可惜。

根本的时候,要用法宝。

本人的宝贝是本身的老妈。这些世界上可见把自己的急需,布署在自己的需求背后的人,只有老妈。

本人给本身妈求助,正中作者妈下怀,说:“房子都收好了。吃的穿的别带,来了后来,她爱好什么样买什么样。早就等着思迪来。”

想来天下祖父母都一律的。

子女和姥姥共处的秋日,不了然到底何人看着何人?不知晓哪位更高兴?

实际性的题材应运而生了,东京(Tokyo)到圣Peter堡,思迪二个伍虚岁半的丫头怎么去吧?

本身当场就说:“去航空集团办个无人陪伴的孩童票,把思迪发过去,简单便捷又利于,一举三得。”

本人妈连声反对,“不行,不行,她才五岁半。你们不大概送她来,小编来接他。”

“她已经5岁半啊,让她练习一下。”我主张已定。

“我们院有个幼童,二〇一八年想办,没办下来,年龄不够。说是要九虚岁。”作者妈的应战格局根本都很迂回。

“好,小编查。”电话没挂,笔者在度娘上查。网络时期,打多少个字的题材。“航空集团规定,年满六岁就可以了。”

小编妈需要给思迪讲话:“你要来马斯喀特了,”思迪欢呼的响动还没叫起来,我妈又说:“你如此个小孩子坐飞机,你怕不怕呀?走丢了如何是好呀?要本人来接你呢?”

器重的事情说三次,思迪害怕了。

于是,思迪睁着大大的眼睛小脸煞白的说:“二姨,我心惊肉跳。小编毫不自个儿坐飞机。”

感情暗示是最强大的事物,尤其是对于心智没有完全成熟的儿女们。

自作者抱着他,给他说了无数大家坐飞机的趣闻。最终语气轻松地说:“你考虑四姨到了二十虚岁了,才第两次和谐坐飞机。因为岳母年龄太大了,都不曾空堂姐姐来领作者,我好伤心吗。”

小姐终于眼睛亮亮的点头说:“好,作者要和精良四嫂玩。”

“嗯,”作者妈没辙了,“那几个题材你要么夜间咨询卢中瀚。”

其一题材历来无须问卢中瀚。

挂了电话,我开头打东航热线。电话很好打,手续也很好办。

那是三个免费的服务,只有三个须要:孩子要满5虚岁,要在至少五十几个小时前约定机票。

东航客服给小编发了三个表格,基本内容就是航班号,孩子的名字,证件号,送机以及接机人姓名证件号,签个名。扫描后发回到东航客服邮箱。再致电热线订孩童票就好了。

自个儿是三个有拖延症的人,心说提前肆拾八个时辰,还有时间,周末再定吧。

周天的时候,那三个拎不清的风暴鸿灿来了,东航热线再也打不通了。打携程,携程说:“那事我们管不了。”

周天东航热线依然打不通。

星期五作者带着思迪去了虹桥机场东航柜台。机场柜台说,“大家不办无人陪同孩童票,你要去东航营业厅,江宁路212号。”

反过来问国航,国航说,“大家从未巴黎飞马那瓜的航班,请去问东航。”

问山航,山航说:“我们亟须通过热线订,这是热线号码。”

虹桥二号航站楼,进门一溜儿的航空集团,一路下来,没有一间可以立刻立即现场办。

我脸绿了,真不是因为作者穿了一条绿裙子。

不大概,依然去了东航江营业厅。本次还倒是挺顺遂。

票买了,思迪知道自身要一人坐飞机去圣彼得堡,整个星期,大姑娘好开心。

周四的时候,作者耐不住她的吵,把小箱子给他拿出去,放在客厅中间,给她说:“本人讨论带哪些,放到箱子里,大妈早晨跟你一块整理。”

自个儿在大力敲电脑的时候,三姑娘忙来忙去的敛着他的东西。丈母娘望着她,笑纹儿爬到了眼睛里。

中午,查看思迪的小箱子。阿姨娘把温馨的小衣服,都叠的大约呈方块状,小玩意儿都装进小箱子里面自备的壹个小袋子里面,还带了牙膏牙刷,写字的台本和画画的笔。真不愧有个射手座的岳丈,从小看大了,那箱子理得真不错。

咱俩一块查了一次,我又装了少数备用的药,孩童防晒霜防蚊水类的东西,基本没有拉长,小姨娘自个儿都想到了。

那就称为,磨练自立自理能力。

星期六早上,思迪欢腾的早上五点半就醒了。

本人找了3个得以挂在身上小布包包。把思迪叫过来,告诉她,这几个小包要随身背着,不探望曾外祖母不可以摘下来。

咱俩一起往包包里面放了,最重点的护照。一块巧克力和一包面巾纸。

童女不同于小小子。尿尿可以站着。

本身交代思迪说,去洗手间,一定无法一直就坐下,请空小姨子姐陪你去,好好给大嫂说,拿面巾纸擦干净再坐。

思迪使劲点头,“固然三嫂无法来,小编要好也会擦干净。然后再洗干净手。”

本身拿了一埃尔克森百块的人民币,叠成四折,说:“那钱不是给你买冰淇淋或许棒棒糖的。那钱是假若必要,你可以拿出去。”

思迪战战兢兢的把钱装进内袋里面,还拉上拉链。拿到巨大财富,臭美得意的神气明显。

坦白好随身的小包,作者还给思迪准备了二个挂在身上的颇负盛名。有她的名字,照片,地址和电话。

整体都准备好,出发去机场。

旅途小编牵着思迪的小手,小手柔软糯糯蜷在自家的手掌里面。心中一动,转头给卢中瀚说:“那还尚无多长期,她连走路都不会,以后竟是能够协调去坐飞机了。”

卢中瀚揉了揉小编的毛发说:“永远长不大的子女,更可怕。”

到了飞机场,爱心服务柜台办好了登记手续。我们竟然的相逢了三个小小叔子也在办无人陪伴的孩童,同一班机去阿塞拜疆巴库。

飞行集团是1个高高瘦瘦冷冷的小伙子来接我们,不太理孩子也不太理我们,闷着头带着男女走。

自身问她,“无人陪同的小不点儿的马甲和贴纸呢?”

他摆摆头说:“柜台没有了。”

我们五个阿姨一步一趋的把孩子们送到安检区。瞧着男女们手舞足蹈的过了安检,拐过弯儿看不到了,才一步一脱胎换骨的相距。

作者和十分小姨一见倾心,互留手机和微信,保持联系。

本身和卢中瀚推着子觅走出航站楼了,子觅突然想到如何,仰初步来,眼睛睁得圆圆说:“思迪呢?”

自个儿给三姨打电话,再一次嘱咐她肯定带着身份证去接机。还差五个多小时,小编妈已经等在国内到达的门口了。

中饭的时候,电话来了。接起来,是思迪的声响,“大妈,我到了。”

叽叽呱呱的给本身讲乘机的经历:空表姐姐们好喜欢她,纷繁跟他合影;小表哥人很好,两个人坐在一起,玩的很快意……

知女莫若母,隔着一千英里的电波,小编也听得出她的兴奋,骄傲与自信。

自作者把思迪自身坐飞机的照片发了个对象圈,那大致是本身收下最多的赞和回复的心上人圈。

有七柒十几个赞,三十几条的死灰复燃。

还有二姑,阿姨只怕朋友尤其微信开窗,来问小编,“怎么如此舍得?孩子安然无恙吗?你是怎么想的?……”

捧在手心怕掉了,含在嘴里怕化了,孩子们自然是大家的宠儿。

不清楚有什么人去看过采蚌的进程。珍珠取出来的时候,圆圆扁扁,有长有短。

实在那一个世界上从未有过天赋长出来,绝美无伦的宝物。

美玉要磨;明珠要磨,钻石更要磨。

当大家给男女说:“你看对门王小胖怎么着?”的时候,或者我们该看看王小胖的老人到底是一种何等的态势?

爱不该是一种借口,担心不可以改为一种约束。

不管男女是逐步照旧很快地来,大妈要做的是,给孩子们留下可以来的退路。

假诺全勤的都是包含万象现成,唾手可来,还有啥理由让儿女们想到向前来?

陆周岁半投机坐飞机,作为三姨自身,怎么能不担心。

不过养孩子,各种人有各类人的尺度。

为此作者安抚好孩子的一丝一毫的恐怖,安插好一切只怕出现的意料之外,按耐住自个儿没辙通晓的担心,微笑着去鼓励她敢于直面,全心为他做出的任何欢呼。

在男女的人生中,小编就是丰富在阴影里面的金牌经纪人。

春风化雨的目标不是高达,而是让他俩自个儿达成。

PS:思迪,你真棒,大伯三姑为你骄傲。

翌日春天,我们本身去法兰西?

无人陪同孩子乘机小贴士:

-无人陪伴小孩子票此服务,各种航空集团免费。

-孩童必须满肆岁稍差于十2周岁。

-购买形式,恐怕航空营业厅,或然致电热线。(旅行社,携程和机场柜台买不停)。

-要超前一定的岁月。推测各个航空公司不一样。东方航空要48小时。

-购买的时候,要出示小孩子自己身份证名,送机人和接机人身份证。接机人身份证提供复印件或然照片就足以。

-乘机前要给孩子做好心情准备。给孩子演习一下有只怕会出现的情形,应该怎样面对。

-给男女准备好1个随身的小包,把主要的东西放进去。

-给子女准备三个身上名牌。

-略大一些儿女,可以设想准备3个电话,可以保持联系。

-找3个子女认识的人去接机。

-最根本的一些,其实是做父母的心情。经常心,轻松欢畅,可是心境慎密,保持警惕。

图文为原创,如需转发,请联系自个儿。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图