Cocos2dx源碼賞析(2)之渲染
這篇,繼續(xù)從源碼的角度來跟蹤下Cocos2dx引擎的渲染過程毅糟,以此來梳理下Cocos2dx引擎是如何將精靈等元素顯示在屏幕上的。
從上一篇對Cocos2dx啟動流程的梳理中可知澜公,Cocos2dx依靠通過各平臺的入口啟動引擎姆另,并在循環(huán)中調(diào)用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;
}
當(dāng)調(diào)用了Director::end()方法時,_purgeDirectorInNextLoop變量才會被置為true坟乾,并執(zhí)行了purgeDirector()方法:
void Director::purgeDirector()
{
reset();
CHECK_GL_ERROR_DEBUG();
// OpenGL view
if (_openGLView)
{
_openGLView->end();
_openGLView = nullptr;
}
// delete Director
release();
}
可以看到迹辐,這里執(zhí)行了一些重置和清理工作。即在需要結(jié)束游戲的時候甚侣,可以調(diào)用Director::end()方法明吩,讓引擎跳出主循環(huán)殷费,執(zhí)行關(guān)閉印荔。
調(diào)用了Director::restart()方法時低葫,_restartDirectorInNextLoop變量會被置為true,即會執(zhí)行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方法中嘿悬,先執(zhí)行了重置reset方法,然后又接著把渲染狀態(tài)水泉、紋理緩存善涨、定時器、內(nèi)存管理草则、動畫等又重新初始化了钢拧。以此來實現(xiàn)游戲重啟的方案。
_invalid變量默認(rèn)是true炕横,剛開始在Director::init中會被置為false娶靡,在調(diào)用Director::stopAnimation()時,會將_invalid置為true看锉,此時不滿足條件姿锭,即不會調(diào)用drawScene()繪制場景的方法,當(dāng)然在調(diào)用Director::startAnimation()又會將_invalid置為false伯铣,由此可以知道呻此,當(dāng)_invalid置為true時,引擎在做空循環(huán)腔寡。
下面焚鲜,才算是真正進(jìn)入主題,即當(dāng)_invalid為false時放前,會調(diào)用drawScene方法來繪制場景忿磅,設(shè)置定時器,動畫凭语,事件循環(huán)等一系列處理:
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方法中葱她,會先調(diào)用calculateDeltaTime方法來計算每幀的時間間隔_deltaTime,即每幀執(zhí)行一系列邏輯操作所花費(fèi)的時間似扔。
接下里判斷了_openGLView吨些,該對象是用來將OpenGL繪制的內(nèi)容呈現(xiàn)在不同平臺對應(yīng)的視圖上,這里不同的平臺有不同的是實現(xiàn)炒辉。而_openGLView的賦值是在調(diào)用了Director::setOpenGLView方法里進(jìn)行的豪墅,而setOpenGLView方法的調(diào)用,我們是在AppDelegate::applicationDidFinishLaunching()方法中調(diào)用的黔寇。所以偶器,這里_openGLView正常情況下是不會為空的。那么,也就會執(zhí)行_openGLView->pollEvents()方法屏轰,這個方法是個空實現(xiàn)颊郎,只在特定的平臺才做相應(yīng)的處理。一般會在該方法中亭枷,檢查有沒觸發(fā)什么事件(鍵盤輸入袭艺、鼠標(biāo)移動等)。
再接著有個_paused的判斷叨粘,而_paused為置為true猾编,即不滿足條件是在調(diào)用了Director::pause方法中設(shè)置的,那么不滿足條件時升敲,就不會執(zhí)行這里的代碼:
if (! _paused)
{
_eventDispatcher->dispatchEvent(_eventBeforeUpdate);
_scheduler->update(_deltaTime);
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
}
也就是當(dāng)調(diào)用了Director::pause的方法答倡,然后進(jìn)入主循環(huán),但是不會響應(yīng)相應(yīng)的事件調(diào)度和定時器的更新處理驴党。
繼續(xù)往下執(zhí)行瘪撇,如下代碼:
_renderer->clear();
experimental::FrameBuffer::clearAllFBOs();
這里,主要是在繪制前港庄,執(zhí)行相應(yīng)的清理工作(例如:清除顏色緩沖區(qū)和深度緩沖區(qū)倔既,清除幀緩沖對象等)。
然后鹏氧,就執(zhí)行這行代碼了:
if (_nextScene)
{
setNextScene();
}
追蹤一下渤涌,可以找到,在調(diào)用了Director的replaceScene把还、pushScene或popScene等方法時实蓬,會給_nextScene賦值,這幾個方法的作用分別是:
replaceScene:將要執(zhí)行的場景壓入場景棧中吊履,并替換當(dāng)前的場景安皱,_nextScene指向要執(zhí)行的場景。
pushScene:將要執(zhí)行的場景壓入場景棧中艇炎,并將_nextScene指向要執(zhí)行的場景酌伊。
popScene:在場景棧中彈出當(dāng)前場景,并將_nextScene指向上一個的場景冕臭。
以上這三個方法都是在下一幀繪制生效腺晾。在setNextScene會執(zhí)行一些場景的狀態(tài)切換,并將下一個要執(zhí)行的場景指定為當(dāng)前運(yùn)行的場景辜贵。
繼續(xù),再就執(zhí)行下面的代碼:
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會將模型視圖的矩陣壓入相應(yīng)的棧中归形。而對應(yīng)的棧有存放模型視圖矩陣的棧托慨,投影矩陣的棧,紋理矩陣的棧暇榴。接下來厚棵,主要看renderScene方法的調(diào)用蕉世。
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是有關(guān)VR的實現(xiàn)婆硬,這里先不關(guān)心狠轻。然后,就調(diào)用到了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方法中彬犯,主要關(guān)心兩個方法的調(diào)用向楼,即下面這兩行代碼:
visit(renderer, transform, 0);
renderer->render();
這里的visit會調(diào)用到父類Node節(jié)點相應(yīng)的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;
}
該方法首先會對當(dāng)前節(jié)點下的子節(jié)點進(jìn)行遍歷并排序,這里遍歷會遍歷整個Node節(jié)點樹谐区,然后在調(diào)用自身的繪制方法draw湖蜕。例如,精靈Sprite會調(diào)用精靈自身的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發(fā)送一個RenderCommand指令(這里是TrianglesCommand),renderer會將RenderCommand放入一個棧中炼杖,等Node節(jié)點元素都遍歷完畢灭返,才執(zhí)行RenderCommand指令。
按照目標(biāo)版本的引擎實現(xiàn)坤邪,就將繪制邏輯從Node節(jié)點樹遍歷中分離出來了熙含。每次繪制就給renderer發(fā)送一個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;
}
這里取出下標(biāo)為0的渲染隊列罩扇,然后婆芦,進(jìn)一步通過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喂饥,并執(zhí)行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的執(zhí)行和相應(yīng)的處理了消约。而在Sprite的繪制發(fā)的是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;
}
這個即是主要的繪制處理以及做相應(yīng)的合并批次的處理或粮。這里,就先寫到這里捞高,簡單的說主要是些OpenGL api的調(diào)用氯材,但是,筆者對這些還沒有深入的理解硝岗,就不“誤人子弟”短条,做過多的分析了恢准,后面等實踐過再來更新此篇,解釋得更詳細(xì)些。因此旋炒,該篇只是勉強(qiáng)對渲染的代碼執(zhí)行流程作了簡單的分析,談不上深入理解。但至少通過閱讀代碼,可以知道相應(yīng)的處理是如何實現(xiàn)的皆看。