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/

版权所有,欢迎保留原文链接进行转载

Cocos2dx源码赏析(2)之渲染的更多相关文章

  1. Cocos2dx源码赏析(4)之Action动作

    Cocos2dx源码赏析(4)之Action动作 本篇,依然是通过阅读源码的方式来简单赏析下Cocos2dx中Action动画的执行过程.当然,这里也只是通过这种方式来总结下对Cocos2dx引擎的理 ...

  2. Cocos2dx源码赏析(1)之启动流程与主循环

    Cocos2dx源码赏析(1)之启动流程与主循环 我们知道Cocos2dx是一款开源的跨平台游戏引擎,而学习开源项目一个较实用的办法就是读源码.所谓,"源码之前,了无秘密".而笔者 ...

  3. Cocos2dx源码赏析(3)之事件分发

    Cocos2dx源码赏析(3)之事件分发 这篇,继续从源码的角度赏析下Cocos2dx引擎的另一模块事件分发处理机制.引擎的版本是3.14.同时,也是学习总结的过程,希望通过这种方式来加深对Cocos ...

  4. [转帖]cocos2D-X源码分析之从cocos2D-X学习OpenGL(3)----BATCH_COMMAND

    原贴: cocos2D-X源码分析之从cocos2D-X学习OpenGL(3)----BATCH_COMMAND 上一篇介绍了QUAD_COMMAND渲染命令,顺带介绍了VAO和VBO,这一篇介绍批处 ...

  5. [转帖]cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND

    原文:cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND 上一篇文章介绍了cocos2d-x的基本渲染结构,这篇顺着之前的渲染结构介绍渲染命令QUA ...

  6. 使用“Cocos引擎”创建的cpp工程如何在VS中调试Cocos2d-x源码

    前段时间Cocos2d-x更新了一个Cocos引擎,这是一个集合源码,IDE,Studio这一家老小的整合包,我们可以使用这个Cocos引擎来创建我们的项目. 在Cocos2d-x被整合到Cocos引 ...

  7. Vue源码后记-vFor列表渲染(1)

    钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...

  8. spring 事务源码赏析(二)

    我们在spring 事务源码赏析(一) 中分析了spring事务是如何找到目标方法,并如何将事务的逻辑织入到我们的业务逻辑中.本篇我们将会看到spring事务的核心实现: 1.事务传播机制的实现 2. ...

  9. spring 事务源码赏析(一)

    在本系列中,我们会分析:1.spring是如何开启事务的.2.spring是如何在不影响业务代码的情况下织入事务逻辑的.3.spirng事务是如何找到相应的的业务代码的.4.spring事务的传播行为 ...

随机推荐

  1. Java并发编程 -- 文章汇总

    文章汇总 1.Thread和Runnable 2.synchronized 3.Lock 4.Executor框架 5.信号量和障碍器 6.Exchanger线程间交换数据 7.Java内存操作总结

  2. HBase学习之路 (二)HBase集群安装

    前提 1.HBase 依赖于 HDFS 做底层的数据存储 2.HBase 依赖于 MapReduce 做数据计算 3.HBase 依赖于 ZooKeeper 做服务协调 4.HBase源码是java编 ...

  3. cpu 基础知识

    认识cpu(中央处理器简称处理器)也叫CPU,Central Processing Unit线程是安排CPU执行的最小单位 四核八线程内涵: 每个单位时间内,一个CPU只能处理一个线程(操作系统:th ...

  4. C语言程序设计I—第六周教学

    第六周教学总结(8/10-14/10) 教学内容 第二章 用C语言编写程序 2.4 输出华氏-摄氏温度转换表 课前准备 在蓝墨云班课发布资源: PTA:2018秋第六周作业 分享码:88C28D89E ...

  5. css笔记--用户界面样式

    1.系统字体,系统颜色.根据关键字设置为系统某方面相同的字体和颜色 2.光标:鼠标移入时光标的不同显示方法,有十字键,手型:cursor:pointer;cursor:hand;要按顺序,cursor ...

  6. C4C有关Browse and Collect的简单用法

    Browse and Collect 最近在研究C4C中的Browse and Collect控件,有点类似于Siebel中的MVG.实现的效果就是在弹窗中简单的从一个BO对象往目标对象中添加数据. ...

  7. jQuery 学习笔记:jQuery 代码结构

    jQuery 学习笔记:jQuery 代码结构 这是我学习 jQuery 过程中整理的笔记,这一部分主要包括 jQuery 的代码最外层的结构,写出来整理自己的学习成果,有错误欢迎指出. jQuery ...

  8. 关于IRAM和IFLASH启动模式,重映射remap 整理中

    工程基于NXP LPC2468 1 为什么试用IRAM MODE 2 设置Program algorithm 编程算法的作用是什么 3 IRAM和FLASH 模式下IROM和IRAM的地址为什么不一样 ...

  9. msfconsole 无法启动,解决办法

    今天突然碰上kali msfconsole 无法启动,经过查找资料,现已成功解决该问题,现将解决办法整理如下: service postgresql start # 启动数据库服务 msfdb ini ...

  10. JavaWeb总结(二)

    Web服务器的缺陷 Web服务器是被设计用来向客户端提供HTTP服务的,它只能向客户端提供静态网页内容.静态页面是原封不动的待在Web服务器目录中,服务器找到静态网页,并把它原样传回到客户端.每个客户 ...