這篇,繼續從源碼的角度來跟蹤下Cocos2dx引擎的渲染過程,以此來梳理下Cocos2dx引擎是如何將精靈等元素顯示在屏幕上的。html
從上一篇對Cocos2dx啓動流程的梳理中可知,Cocos2dx依靠經過各平臺的入口啓動引擎,並在循環中調用Director::mainLoop方法來維持引擎的各類邏輯:node
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()方法:git
void Director::purgeDirector() { reset(); CHECK_GL_ERROR_DEBUG(); // OpenGL view if (_openGLView) { _openGLView->end(); _openGLView = nullptr; } // delete Director release(); }
能夠看到,這裏執行了一些重置和清理工做。即在須要結束遊戲的時候,能夠調用Director::end()方法,讓引擎跳出主循環,執行關閉。github
調用了Director::restart()方法時,_restartDirectorInNextLoop變量會被置爲true,即會執行restartDirector()方法:api
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時,引擎在作空循環。app
下面,纔算是真正進入主題,即當_invalid爲false時,會調用drawScene方法來繪製場景,設置定時器,動畫,事件循環等一系列處理:ide
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,即每幀執行一系列邏輯操做所花費的時間。oop
接下里判斷了_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/ 版權全部,歡迎保留原文連接進行轉載 :)