iOS消息推送机制

推送通知和NSNotification不同
1.NSNotification凡空洞的,不可见的
2.推送通知是可见的

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->pollEvents()方法,这个方法是单空实现,只于一定的平台才做相应的拍卖。一般会以拖欠办法吃,检查来没有点发什么风波(键盘输入、鼠标移动等)。

再度接着发生只_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是有关VR的落实,这里先不体贴。然后,就调用到了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。例如,精灵Sprite会调用精灵自身之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
    }
}

以Sprite的draw方法中,并无直接绘制,而是为renderer发送一个RenderCommand指令(这里是TrianglesCommand),renderer会将RenderCommand放入一个栈中,等Node节点元素都遍历完毕,才实施RenderCommand指令。

遵目标版本的引擎实现,就用绘制逻辑从Node节点树遍历中分别出去了。每次绘制就给renderer发送一个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的实施及对应的拍卖了。而在Sprite的绘图发之是TRIANGLES_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/
版权所有,欢迎保留原来文链接进行转载 🙂

iOS中提供了2挨推送通知
1.地面推送通知(Local Notification)
2.远程推送通知(Remote Notification)

推送的意图:可以让非以前台运行的app,告知客户app内部发生的事情.(QQ音推送,微信消息推送等等)

推送通知之变现效果:
1.以屏幕顶部显示的同长长的横幅
2.在屏幕中间弹来一个UIAlertView
3.当锁屏界面显示一片横幅
4.及新app图标的数字
5.广播音效

本地通知

1.无欲服务器支持(无需联网)就能够出的推送通知
2.用到状况: 定时类任务(闹钟,简单的玩之类)

当地通知推送的实现好简单:
1.创本地推送通知对象
[[UILocalNotification alloc] init]创办一个本地通知
2.装置本地通知的相干属性
非得设置的特性
2.1.推送通知之触发时(何时来推送通知)
@property(nonatomic,copy) NSDate *fireDate
2.2.推送通知的具体内容
@property(nonatomic,copy) NSString *alertBody
2.3.当锁屏时显示的动作标题(完整测标题:”滑动来” + alertAction)
@property(nonatomic,copy) NSString *alertAction
2.4.安装锁屏界面alertAction是否管用
localNote.hasAction = YES;
2.5.app图标数字
@property(nonatomic,assign) NSInteger applicationIconBadgeNumber
2.6.调度本地推送通知(调度了后,推动通知会见于一定时刻fireDate发出)
[[UIApplication shareApplication] scheduleLocalNotification:ln]
可以展开安装的装置
2.7.装置通知中心通报的题目
localNote.alertTitle = @"222222222222";
2.8.装音效(如果无安装就是系默认的音效,
设置的语会于mainBundle中查找)
localNote.soundName = @"buyao.wav";
2.9.各国隔多久又发一样次推送通知
@property(nonatomic) NSCalendarUnit repeatInterval
2.10.点撞推送通知打开app时显得的启动图片(mainBundle 中提取图片)
@property(nonatomic,copy) NSSring *alertLaunchImage
2.11.增大的额外信息
@property(nonatomic,copy) NSDictionary *userInfo
2.12.时区
@property(nonatomic,copy) NSTimeZone *timeZone
(一般安装也[NSTimeZone defaultTimeZone],跟随手机的时区)

–代码实现过程:

当地通知.gif

/*
 @property(nonatomic,copy) NSDate *fireDate;
 @property(nonatomic,copy) NSTimeZone *timeZone; 时区

 @property(nonatomic) NSCalendarUnit repeatInterval; 重复间隔(枚举)
 @property(nonatomic,copy) NSCalendar *repeatCalendar; 重复日期(NSCalendar)

 @property(nonatomic,copy) CLRegion *region 设置区域(设置当进入某一个区域时,发出一个通知)

 @property(nonatomic,assign) BOOL regionTriggersOnce YES,只会在第一次进入某一个区域时发出通知.NO,每次进入该区域都会发通知

 @property(nonatomic,copy) NSString *alertBody;      

 @property(nonatomic) BOOL hasAction;                是否隐藏锁屏界面设置的alertAction
 @property(nonatomic,copy) NSString *alertAction;    设置锁屏界面一个文字

 @property(nonatomic,copy) NSString *alertLaunchImage;   启动图片
 @property(nonatomic,copy) NSString *alertTitle

 @property(nonatomic,copy) NSString *soundName;

 @property(nonatomic) NSInteger applicationIconBadgeNumber;

 @property(nonatomic,copy) NSDictionary *userInfo; // 设置通知的额外的数据
 */

- (IBAction)addLocalNote:(id)sender {
    // 1.创建一个本地通知
    UILocalNotification *localNote = [[UILocalNotification alloc] init];

    // 2.设置本地通知的一些属性(通知发出的时间/通知的内容)
    // 2.1.设置通知发出的时间
    localNote.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0];
    // 2.2.设置通知的内容
    localNote.alertBody = @"吃饭了吗?";
    // 2.3.设置锁屏界面的文字
    localNote.alertAction = @"查看具体的消息";
    // 2.4.设置锁屏界面alertAction是否有效
    localNote.hasAction = YES;
    // 2.5.设置通过点击通知打开APP的时候的启动图片(无论字符串设置成什么内容,都是显示应用程序的启动图片)
    localNote.alertLaunchImage = @"111";
    // 2.6.设置通知中心通知的标题
    localNote.alertTitle = @"222222222222";
    // 2.7.设置音效
    localNote.soundName = @"buyao.wav";
    // 2.8.设置应用程序图标右上角的数字
    localNote.applicationIconBadgeNumber = 1;
    // 2.9.设置通知之后的属性
    localNote.userInfo = @{@"name" : @"张三", @"toName" : @"李四"};

    // 3.调度通知
    [[UIApplication sharedApplication] scheduleLocalNotification:localNote];
}

当消息让推送过来时,我们用点击推送消息,来完成有一定的任务.不如更新界面什么的(监听本地推送通知的点击)

当用户点击本地推送通知之时光,会自动打开app,这里来2种情形

1.app没有关,只是直接藏在后台
让app进入前台,并会见调用AppDelegate的下边的章程(甭更起动app)

点击本地通知.gif

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
—-代码实现

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    // 在这里写跳转代码
    // 如果是应用程序在前台,依然会收到通知,但是收到通知之后不应该跳转
    if (application.applicationState == UIApplicationStateActive) return;

    if (application.applicationState == UIApplicationStateInactive) {
        // 当应用在后台收到本地通知时执行的跳转代码
        [self jumpToSession];
    }

    NSLog(@"%@", notification);
}

- (void)jumpToSession
{
    UILabel *redView = [[UILabel alloc] init];
    redView.backgroundColor = [UIColor redColor];
    redView.frame = CGRectMake(0, 100, 300, 400);
    redView.numberOfLines = 0;
    // redView.text = [NSString stringWithFormat:@"%@", launchOptions];
    [self.window.rootViewController.view addSubview:redView];
}

2.app已经被关闭(进程被杀)

点击本地通知.gif

起步app,启动了会调用AppDelegate的底下的不二法门
- (BOOL)application:(UIApplication *)application didFinishLaunchWithOptions:(NSDictionary *)launchOptions;
launchOptions参数经UIApplicationLaunchOptionsLocalNotificationKey取出本地推送通知对象
得特别注意的凡:在iOS8.0随后本地通知来了片转变,如果要动当地通知,需要获得用户的许可.
didFinishLaunchWithOptions方式中上加如下代码:

#define IS_iOS8 ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)

 if (IS_iOS8) {
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound categories:nil];
        [application registerUserNotificationSettings:settings];
    }

—–代码实现相关操作

#define IS_iOS8 ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    /*
     UIUserNotificationTypeNone    = 0,      不发出通知
     UIUserNotificationTypeBadge   = 1 << 0, 改变应用程序图标右上角的数字
     UIUserNotificationTypeSound   = 1 << 1, 播放音效
     UIUserNotificationTypeAlert   = 1 << 2, 是否运行显示横幅
     */

    [application setApplicationIconBadgeNumber:0];

    if (IS_iOS8) {
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound categories:nil];
        [application registerUserNotificationSettings:settings];
    }

    // 如果是正常启动应用程序,那么launchOptions参数是null
    // 如果是通过其它方式启动应用程序,那么launchOptions就值
    if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {
        // 当被杀死状态收到本地通知时执行的跳转代码
        // [self jumpToSession];
        UILabel *redView = [[UILabel alloc] init];
        redView.backgroundColor = [UIColor redColor];
        redView.frame = CGRectMake(0, 100, 300, 400);
        redView.numberOfLines = 0;
        redView.text = [NSString stringWithFormat:@"%@", launchOptions];
        [self.window.rootViewController.view addSubview:redView];
    }

    return YES;
}
长途推送(Remote Notification)

1.由远程服务器推送给客户端的通(需要联网)
2.远程推送服务, 苹果于名叫也:APNS (Apple Push Notification Services)
缓解问题:只要联网了, 就能够接及服务器推送的长途通知
采用须知:
持有的苹果设备,在联网状态下,都见面和苹果服务器建立长连接.
1.长总是:一直连,客户端与服务器
2.长连接图:
1>事件校准
2>系统升级
3>查找自己的iPhone等….
3.长连接的补益
1>数据传输速度快
2>数据保持最新状态

官结果长连的采用

1.获得deviceToken的过程

Snip20151005_1.png

Snip20151005_2.png

1>客户端向苹果服务APNS,发送设备的UDID和英语的Bundle Identifier.
2>经苹果服务器加密生成一个deviceToken
3>将目前用户之deviceToken(用户标识),发送给好运的服务器
4>自己之服务器,将取得的deviceToken,进行封存

2.利用deviceToken进展数据传,推送通知

Snip20151005_3.png

5>需要推送的当儿,将消息和deviceToken一起发送给APNS,苹果服务器,再经过deviceToken找到用户,并将信息发给用户

这里不再演示关于证书之布置, 简单的才进行验证步骤:
1> 创建明确的AppID,只有明确的AppID才能够进行部分特殊之操作
2>真机调试的APNS SSL证书
3>发布程序的APNS SSL证书
4>生成描述文件
[逐条安装证书, 再装描述]

报远程推送通知:

1.客户端如果想要接收APNs的长途推送通知,必须事先进行注册(得到用户授权)
诚如在APP启动了后即即刻开展注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) {
        // 1.注册UserNotification,以获取推送通知的权限
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge categories:nil];
        [application registerUserNotificationSettings:settings];

        // 2.注册远程推送
        [application registerForRemoteNotifications];
    } else {
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
    }

    return YES;
}

2.挂号成功后, 调用AppDelegate的道,获取到用户葡京游戏网址的deviceToken

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // <32e7cf5f 8af9a8d4 2a3aaa76 7f3e9f8e 1f7ea8ff 39f50a2a e383528d 7ee9a4ea>
    // <32e7cf5f 8af9a8d4 2a3aaa76 7f3e9f8e 1f7ea8ff 39f50a2a e383528d 7ee9a4ea>
    NSLog(@"%@", deviceToken.description);
}

3.点击推送通知,和本土同有少种植状况.
1> app没有关,只是直隐匿于后台
让app进入前台, 并调用下面的办法(app没有还启航)
过期的点子:

// 当接受到远程退职时会执行该方法(当进入前台或者应用程序在前台)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSLog(@"%@", userInfo);

    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    redView.frame = CGRectMake(100, 100, 100, 100);
    [self.window.rootViewController.view addSubview:redView];
}

苹果系建议以下的法门:

/*
 1.开启后台模式
 2.调用completionHandler,告诉系统你现在是否有新的数据更新
 3.userInfo添加一个字段:"content-available" : "1" : 只要添加了该字段,接受到通知都会在后台运行
 */
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    NSLog(@"%@", userInfo);
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    redView.frame = CGRectMake(100, 100, 100, 100);
    [self.window.rootViewController.view addSubview:redView];

    completionHandler(UIBackgroundFetchResultNewData);
}

2>app已经关,需要再开,—基本实现方式与地方通知yi’zhi