热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Cocos2dx源码赏析(2)之渲染

Cocos2dx源码赏析(2)之渲染这篇,继续从源码的角度来跟踪下Cocos2dx引擎的渲染过程,以此来梳理下Cocos2dx引擎是如何将精灵等元素显示在屏幕上的。从上一篇对Cocos2dx启动流

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 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 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 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 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 {
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 {
//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(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 for vertex is not big enough, please break the data down or use customized render command");
CCASSERT(cmd->getIndexCount()>= 0 && cmd->getIndexCount() "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(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(command);
CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_CUSTOM_COMMAND");
cmd->execute();
}
else if(RenderCommand::Type::BATCH_COMMAND == commandType)
{
flush();
auto cmd = static_cast(command);
CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_COMMAND");
cmd->execute();
}
else if(RenderCommand::Type::PRIMITIVE_COMMAND == commandType)
{
flush();
auto cmd = static_cast(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 {
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/
版权所有,欢迎保留原文链接进行转载 :)


推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • php缓存ri,浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)(日常整理)
    thinkPHP的F方法只能用于缓存简单数据类型,不支持有效期和缓存对象。S()缓存方法支持有效期,又称动态缓存方法。本文是小编日常整理有关thinkp ... [详细]
  • 本文整理了Java中com.evernote.android.job.JobRequest.getTransientExtras()方法的一些代码示例,展示了 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • Asp.net Mvc Framework 七 (Filter及其执行顺序) 的应用示例
    本文介绍了在Asp.net Mvc中应用Filter功能进行登录判断、用户权限控制、输出缓存、防盗链、防蜘蛛、本地化设置等操作的示例,并解释了Filter的执行顺序。通过示例代码,详细说明了如何使用Filter来实现这些功能。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 本文介绍了5个基本Linux命令行工具的现代化替代品,包括du、top和ncdu。这些替代品在功能上进行了改进,提高了可用性,并且适用于现代化系统。其中,ncdu是du的替代品,它提供了与du类似的结果,但在一个基于curses的交互式界面中,重点关注占用磁盘空间较多的目录。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
author-avatar
守护琳的心
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有