【cocos2d-x 源码解析】创建 OpenGL 环境

创建 OpenGL 上下文

cocos2d-x 是基本 OpenGL 实现的,所以游戏启动之后做的第一件事就是创建 OpenGL 上下文,之后才能进行渲染。在 win32 上,创建 OpenGL context 的过程就是创建一个 OpenGL 窗口,cocos2d-x 使用的是 glfw 库,首先我们回顾一下 glfw 创建窗口的过程

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
    GLFWwindow* window = glfwCreateWindow(400, 400, "Color", nullptr, nullptr);
    if (window == nullptr) { return 1; }
    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    if (GLEW_OK != glewInit()) { return 2; }

    glClearColor(255, 255, 255, 255);
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT);
        //render
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

这是 glfw 创建 OpenGL 窗口、调用 OpenGL 渲染指令以及监听处理各种事件的过程,接下来看看 cocos2d-x 中是如何使用 glfw 的。

回顾窗口启动

关于 cocos2d-x 的窗口启动过程可以看我前面的文章
cocos2d-x 窗口启动
下面简单的回顾一下这个过程。在 main 函数创建一个 AppDelegate 实例之后,调用 Application 的 run 方法启动游戏

int Application::run()
{
    //...
    initGLContextAttrs();

    if (!applicationDidFinishLaunching())
    {
        return 1;
    }

    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();
    glview->retain();

    while(!glview->windowShouldClose())
    {
        //...
        director->mainLoop();
        glview->pollEvents();
    }

    if (glview->isOpenGLReady())
    {
        director->end();
        director->mainLoop();
        director = nullptr;
    }
    glview->release();
    return 0;
}

这是 run 方法的关键代码,首先调用 initGLContextAttrs 设置 OpenGL context 的属性,这个方法在其子类 Appdelegate 中实现

void AppDelegate::initGLContextAttrs()
{
    GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};
    GLView::setGLContextAttrs(glContextAttrs);
}

目前 cocos2d-x 只能设置六个属性,分别是 rgba 四个颜色属性,深度和模板;定义一个 GLContextAttrs 变量,然后调用 GLView 的静态方法进行设置,GLView 是我们这篇文章的重点,下面会解析这个类。设置完 OpenGL Context 属性之后,调用 applicationDidFinishLaunching 方法进行一些初始化,这个方法也是在子类 AppDelegate 中实现

bool AppDelegate::applicationDidFinishLaunching() {
    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();
    if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
        glview = GLViewImpl::createWithRect("CppSource", Rect(0, 0, designResolutionSize.width, designResolutionSize.height));
#else
        glview = GLViewImpl::create("CppSource");
#endif
        director->setOpenGLView(glview);
    }
    //...
    return true;
}

applicationDidFinishLaunching 做了很多初始化工作,其中最重要的就是初始化导演实例 Director 和 OpenGL 窗口管理类实例 GLView。Director 对象中有一个 GLView 实例,该实例默认为空,调用 GLViewImpl 的 createWithRect 创建一个实例。GLViewImpl 继承自 GLView,是桌面程序的 OpenGL 窗口管理类,而 GLView 则是一个上层的 OpenGL 窗口管理类,提供部分接口,更多的接口则在 GLViewImpl 中实现。GLView 和 GLViewImpl 的目录结构

|-cocos
    |-platform
        |-CCGLView.h
        |-CCGLView.cpp
        |-desktop
            |-CCGLViewImpl-desktop.h
            |-CCGLViewImpl-desktop.cpp

applicationDidFinishLaunching 执行完成之后就得到了一个 Director 实例和一个 GLViewImpl 实例,OpenGL 窗口也创建出来了,OpenGL context 也存在了,可以说 OpenGL 环境已经具备了,接下来就可以开始渲染了。在 while 循环中调用 GLView 的 windowShouldClose 检测 OpenGL 窗口是否关闭,如果关闭了则跳出循环,做一些回收操作然后程序结束,如果没关闭则程序在这里阻塞,每一帧都会进入到这个 while 循环体中,这个循环体做的事情就是监听事件和 OpenGL 渲染。

GLView(GLViewImpl)

接下来就是主角 GLView(GLViewImpl) 登场了,首先看 GLViewImpl 的构造函数

GLViewImpl::GLViewImpl(bool initglfw)
: _captured(false)
, _supportTouch(false)
, _isInRetinaMonitor(false)
, _isRetinaEnabled(false)
, _retinaFactor(1)
, _frameZoomFactor(1.0f)
, _mainWindow(nullptr)
, _monitor(nullptr)
, _mouseX(0.0f)
, _mouseY(0.0f)
{
    _viewName = "cocos2dx";
    g_keyCodeMap.clear();
    for (auto& item : g_keyCodeStructArray)
    {
        g_keyCodeMap[item.glfwKeyCode] = item.keyCode;
    }

    GLFWEventHandler::setGLViewImpl(this);
    if (initglfw)
    {
        glfwSetErrorCallback(GLFWEventHandler::onGLFWError);
        glfwInit();
    }
}

这里做了两件重要的事,第一件就是初始化 GLFWEventHandler;第二件就是执行 glfwInit(),也就是我们熟悉的 glfw 初始化了。接下来看GLFWEventHandler 这个类,这个类在 CCGLViewImpl-desktop.cpp 中定义,它的内容很简单

class GLFWEventHandler
{
public:
    static void onGLFWError(int errorID, const char* errorDesc)
    {
        if (_view)
            _view->onGLFWError(errorID, errorDesc);
    }

    static void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify)
    {
        if (_view)
            _view->onGLFWMouseCallBack(window, button, action, modify);
    }

    static void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y)
    {
        if (_view)
            _view->onGLFWMouseMoveCallBack(window, x, y);
    }

    static void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y)
    {
        if (_view)
            _view->onGLFWMouseScrollCallback(window, x, y);
    }

    static void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
    {
        if (_view)
            _view->onGLFWKeyCallback(window, key, scancode, action, mods);
    }

    static void onGLFWCharCallback(GLFWwindow* window, unsigned int character)
    {
        if (_view)
            _view->onGLFWCharCallback(window, character);
    }

    static void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y)
    {
        if (_view)
            _view->onGLFWWindowPosCallback(windows, x, y);
    }

    static void onGLFWframebuffersize(GLFWwindow* window, int w, int h)
    {
        if (_view)
            _view->onGLFWframebuffersize(window, w, h);
    }

    static void onGLFWWindowSizeFunCallback(GLFWwindow *window, int width, int height)
    {
        if (_view)
            _view->onGLFWWindowSizeFunCallback(window, width, height);
    }

    static void setGLViewImpl(GLViewImpl* view)
    {
        _view = view;
    }

    static void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified)
    {
        if (_view)
        {
            _view->onGLFWWindowIconifyCallback(window, iconified);
        }
    }

private:
    static GLViewImpl* _view;
};

可以看到,这是一个只有静态方法的工具类,用于 glfw 的事件回调处理。首先调用 setGLViewImpl 保存一个 GLViewImpl 实例,然后注册 glfw 事件时直接使用这个类的静态方法作为回调函数,当事件触发时就会调用这个类的静态方法,然后再调用 _view 相应的方法,这样就达到了将 GLViewImpl 的方法注册给事件的目的,所以 GLFWEventHandle 其实是一个中介,将 glfw 事件回调和 GLViewImpl 的实例方法联系起来。

总结起来,GLViewImpl 的构造函数做了这三件事件,初始化 GLFWEventHandler,注册一个错误回调函数,初始化 glfw。接下来回到 createWithRect 方法

GLViewImpl* GLViewImpl::createWithRect(const std::string& viewName, Rect rect, float frameZoomFactor)
{
    auto ret = new (std::nothrow) GLViewImpl;
    if(ret && ret->initWithRect(viewName, rect, frameZoomFactor)) {
        ret->autorelease();
        return ret;
    }
    CC_SAFE_DELETE(ret);
    return nullptr;
}

这个方法也很简单,new 一个 GLViewImpl 实例,然后调用 initWithRect 方法

bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor)
{
    setViewName(viewName);

    _frameZoomFactor = frameZoomFactor;

    glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);
    glfwWindowHint(GLFW_RED_BITS,_glContextAttrs.redBits);
    glfwWindowHint(GLFW_GREEN_BITS,_glContextAttrs.greenBits);
    glfwWindowHint(GLFW_BLUE_BITS,_glContextAttrs.blueBits);
    glfwWindowHint(GLFW_ALPHA_BITS,_glContextAttrs.alphaBits);
    glfwWindowHint(GLFW_DEPTH_BITS,_glContextAttrs.depthBits);
    glfwWindowHint(GLFW_STENCIL_BITS,_glContextAttrs.stencilBits);

    int needWidth = rect.size.width * _frameZoomFactor;
    int neeHeight = rect.size.height * _frameZoomFactor;

    _mainWindow = glfwCreateWindow(needWidth, neeHeight, _viewName.c_str(), _monitor, nullptr);

    //...

    int realW = 0, realH = 0;
    glfwGetWindowSize(_mainWindow, &realW, &realH);

    //...

    glfwMakeContextCurrent(_mainWindow);

    glfwSetMouseButtonCallback(_mainWindow, GLFWEventHandler::onGLFWMouseCallBack);
    glfwSetCursorPosCallback(_mainWindow, GLFWEventHandler::onGLFWMouseMoveCallBack);
    glfwSetScrollCallback(_mainWindow, GLFWEventHandler::onGLFWMouseScrollCallback);
    glfwSetCharCallback(_mainWindow, GLFWEventHandler::onGLFWCharCallback);
    glfwSetKeyCallback(_mainWindow, GLFWEventHandler::onGLFWKeyCallback);
    glfwSetWindowPosCallback(_mainWindow, GLFWEventHandler::onGLFWWindowPosCallback);
    glfwSetFramebufferSizeCallback(_mainWindow, GLFWEventHandler::onGLFWframebuffersize);
    glfwSetWindowSizeCallback(_mainWindow, GLFWEventHandler::onGLFWWindowSizeFunCallback);
    glfwSetWindowIconifyCallback(_mainWindow, GLFWEventHandler::onGLFWWindowIconifyCallback);

    setFrameSize(rect.size.width, rect.size.height);

    //...

    initGlew();

    glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
    return true;
}

在这个方法里看到很多熟悉的 glfw 函数,首先调用 glfwWindowHint 设置一些参数,然后调用 glfwCreateWindow 创建窗口,调用 glfwMakeContextCurrent 将创建出来的窗口设置为 OpenGL 的当前上下文;之后是注册各种事件,可以看到事件的回调函数都是 GLFWEventHandler 的静态方法,最后调用 initGlew 初始化 glew

bool GLViewImpl::initGlew()
{
#if (CC_TARGET_PLATFORM != CC_PLATFORM_MAC)
    GLenum GlewInitResult = glewInit();
    if (GLEW_OK != GlewInitResult)
    {
        MessageBox((char *)glewGetErrorString(GlewInitResult), "OpenGL error");
        return false;
    }
    //...

    return true;
}

initGlew 只是简单地调用 glewInit 函数,然后加一些成功与否判断而已。GLViewImpl 的构造函数和 initWithRect 方法,完成了 glfw 和 glew 的初始化工作,创建一个 OpenGL 窗口并设置 OpenGL context,注册各种 glfw 事件,所以我们可以说执行完 AppDelegate 的 applicationDidFinishLuanching 方法之后,OpenGL 环境已全部初始完成。

接下来回到 Application 的 run 方法,调用 applicationDidFinishLuanching 方法初始化 OpenGL 环境之后,在 while 循环条件体不断调用 GLView 的 windowShouldClose 方法检测窗口是否关闭

bool GLViewImpl::windowShouldClose()
{
    if(_mainWindow)
        return glfwWindowShouldClose(_mainWindow) ? true : false;
    else
        return true;
}

就是简单地调用 glfwWindowShouldClose 函数而已。如果窗口没有关闭,则循环执行两件事,监听事件和渲染,监听事件也很简单

void GLViewImpl::pollEvents()
{
    glfwPollEvents();
}

而渲染则是调用 Director 的 mainLoop 方法

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();

        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

Director 中有两个控制变量,_purgeDirectorInNextLoop 为真时清除导演,_restartDirectorInNextLoop 为真时重置导演,两个变量都为假时才调用 drawScene,很明显 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
        _runningScene->render(_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 计算帧间隔时间,然后调用 glfwPollEvents 监听事件,最后执行调度器 scheduler 的 update 方法。第二部分只做一件事,检测是否需要切换到下个场景,如果需要则调用 setNextScene 方法设置 _runningScene 的值为 _nextScene。第三部分开始真正绘制场景,在导演 Director 中有两个变量,_runningScene 和 _render,分别表示当前运行的场景和渲染器。场景渲染的流程如下
* 第一步,如果使用了物理引擎或者 3D 导航 api,则调用 setPhysicsAndNavigation 方法执行相应的 update 方法
* 第二步,调用渲染器的 clearDrawStats 方法清除绘制统计
* 第三步,调用场景的 render 方法绘制场景中的所有对象
* 第四步,如果 _displayStats 为真则绘制调试信息
* 第五步,调用 glfwSwapBuffers 完成执行 OpenGL 渲染

其中最重要的是第三步,即场景的 render 方法

void Scene::render(Renderer* renderer)
{
    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;
        }

        director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());
        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();

        director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    }

#if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION
    if (_physics3DWorld && _physics3DWorld->isDebugDrawEnabled())
    {
        director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _physics3dDebugCamera != nullptr ? _physics3dDebugCamera->getViewProjectionMatrix() : defaultCamera->getViewProjectionMatrix());
        _physics3DWorld->debugDraw(renderer);
        renderer->render();
        director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    }
#endif

    Camera::_visitingCamera = nullptr;
    experimental::FrameBuffer::applyDefaultFBO();
}

首先取到场景的摄像机 Camera,然后调用 Camera.apply 方法填充 FBO 对象和设置视口

void Camera::apply()
{
    applyFrameBufferObject();
    applyViewport();
}

void Camera::applyViewport()
{
    if(nullptr == _fbo)
    {
        glViewport(getDefaultViewport()._left, getDefaultViewport()._bottom, getDefaultViewport()._width, getDefaultViewport()._height);
    }
    else
    {
        glViewport(_viewport._left * _fbo->getWidth(), _viewport._bottom * _fbo->getHeight(),
                   _viewport._width * _fbo->getWidth(), _viewport._height * _fbo->getHeight());
    }

}

然后调用 Camera.clearBackground 方法绘制背景

void Camera::clearBackground()
{
    if (_clearBrush)
    {
        _clearBrush->drawBackground(this);
    }
}

void CameraBackgroundDepthBrush::drawBackground(Camera* camera)
{
    //...

    _glProgramState->setUniformFloat("depth", _depth);
    _glProgramState->apply(Mat4::IDENTITY);
    GLshort indices[6] = {0, 1, 2, 3, 2, 1};

    {
        GL::bindVAO(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

        // vertices
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), &_quad.tl.vertices);

        // colors
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F), &_quad.tl.colors);

        // tex coords
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), &_quad.tl.texCoords);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
    }
    //..
}

在这个方法里我们终于看到了一些 OpenGL 的指令,比如 bindVAO 绑定顶点数组对象,glBindBuffer 绑定索引缓冲对象,glVertexAttribPointer 链接顶点属性,glDrawElements 使用索引绘制。接下来是调用 visit 方法,这个方法在基类 Node 中定义

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( ; i < _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; it != _children.cend(); ++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;
}

这个方法很长,它的作用就是递归地调用每一个子结点,然后执行每个结点的 draw 方法,每种需要绘制的结点都实现了 draw 方法,我们以 Sprite 为例

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    //...
    {
        _trianglesCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags);
        renderer->addCommand(&_trianglesCommand);
        //...
    }
}

其核心代码就是创建一个渲染指令,然后将指令添加到渲染器 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;
}

这个方法先对所有渲染组进行排列,然后渲染第一个组,调用 visitRenderQueue 进行渲染

void Renderer::visitRenderQueue(RenderQueue& queue)
{
    queue.saveRenderState();

    const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);
    if (zNegQueue.size() > 0)
    {
        //...
        for (auto it = zNegQueue.cbegin(); it != zNegQueue.cend(); ++it)
        {
            processRenderCommand(*it);
        }
        flush();
    }
    //...
}

这又是一个特别特别长的方法,其核心代码就是遍历渲染指令,然后调用 processRenerCommand 执行该指令

void Renderer::processRenderCommand(RenderCommand* command)
{
    auto commandType = command->getType();
    if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
    {
        //Draw if we have batched other commands which are not triangle command
        flush3D();
        flushQuads();

        //Process triangle command
        auto cmd = static_cast<TrianglesCommand*>(command);

        //Draw batched Triangles if necessary
        if(cmd->isSkipBatching() || _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");
            //Draw batched Triangles if VBO is full
            drawBatchedTriangles();
        }

        //Batch Triangles
        _batchedCommands.push_back(cmd);

        fillVerticesAndIndices(cmd);

        if(cmd->isSkipBatching())
        {
            drawBatchedTriangles();
        }

    }
    //else if(...)
}

在 processRenderCommand 方法中就可以看到很多绘制方法了,我们随便打开一个诸如 drawBatchedTriangles 的方法,可以看到全是 OpenGL 指令

总结

  • 在 Application 的 run 方法中调用 AppDelegate 的 applicationDidFinishLaunching 方法,在这个方法中创建并初始化 GLView,这个过程就是创建和初始化 OpenGL 的过程,经过这过程之后,一个 OpenGL 窗口就被创建出来了
  • 在 Application 的 run 方法中调用 GLView 的 windowShouldClose 方法检测窗口是否关闭,如果未关闭则都调用渲染指令
  • Driector 的 drawScene 负责绘制渲染,首先拿到当前场景的摄像机并设置视口,然后递归遍历所有结点并调用结点的 draw 方法
  • 结点的 draw 方法创建一个渲染 command 并把这个 command 添加到渲染器 renderer 中去
  • 在 Driector 的 drawScene 方法中调用渲染器的 render 方法进行渲染,render 方法取到渲染指令后调用 processRenderCommand 执行 OpenGL 指令
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: cocos2d-x是一款流行的开源游戏引擎,它可以用作开发本地移动游戏和桌面游戏。利用cocos2d-x游戏源码,开发人员可以快速构建流畅、高效、具有吸引力的游戏。该引擎使用C ++语言和脚本语言Lua来实现游戏开发,用户可以根据自己的需要进行选择。 cocos2d-x游戏源码包含许多强大的功能和工具,例如精灵、动画、场景管理、碰撞检测和音频控制等。通过这些功能,开发人员可以方便地创建各种类型的游戏,例如动作游戏、射击游戏、冒险游戏和益智游戏等。 此外,cocos2d-x游戏源码还具有高度定制化的特性,允许开发人员根据他们的需求自定义游戏元素。这种定制化可以通过改进游戏画面,添加新的模式和关卡,甚至整个新的游戏玩法来完成。 总之,cocos2d-x游戏源码是一款功能强大、易于使用和高度定制的游戏引擎,它可以帮助开发人员快速开发各种类型的游戏。无论你是专业开发人员还是无经验的游戏制作者,cocos2d-x都是一个极好的选择。 ### 回答2: Cocos2d-x游戏源码是使用Cocos2d-x引擎进行开发的游戏程序代码。Cocos2d-x引擎是一个开源的跨平台游戏引擎,可以支持iOS、Android、Windows Phone、Windows、MacOS和Linux等多个平台,并且提供了丰富的游戏开发API和工具集。Cocos2d-x游戏源码包含了游戏的核心逻辑、UI设计、动画效果、音频效果等各方面的代码,可以作为开发者学习和借鉴的重要资料。 由于Cocos2d-x引擎支持多种编程语言(如C++、Lua等),因此Cocos2d-x游戏源码也会有对应的编程语言版本。开发者可以通过阅读Cocos2d-x游戏源码,了解游戏开发过程中的技术细节,学习如何使用Cocos2d-x引擎进行游戏开发。同时,开发者也可以通过对Cocos2d-x游戏源码进行修改和优化,来满足游戏的特定需求,提升游戏的性能和用户体验。 总之,Cocos2d-x游戏源码是游戏开发者必备的重要资源,可以帮助开发者更好地了解游戏开发技术,提升游戏开发水平。 ### 回答3: cocos2d-x是一款优秀的游戏开发引擎,其源码包含了许多功能强大的游戏开发工具和API。使用cocos2d-x可以帮助开发者更快速更高效地开发出优秀的游戏作品。 cocos2d-x的游戏源码非常丰富,其包含了许多不同类型的游戏示例和模板,如平面射击、塔防、解谜等,这些示例可以帮助开发者更好地了解cocos2d-x的使用方法和原理。 cocos2d-x源码还包含了许多强大的API和工具,例如场景管理、动画控制、音频引擎等,这些工具和API能够帮助开发者更好地完成游戏的开发和调试。 此外,cocos2d-x源码还提供了完整的游戏开发框架,包括资源管理、内存管理、事件机制等,这些框架可以帮助开发者更好地组织代码和提高代码的可维护性。 总的来说,cocos2d-x游戏源码提供了丰富的工具和API,可以帮助开发者更高效地进行游戏开发,大大降低了开发难度和成本,是一款非常适合游戏开发者使用的引擎。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值