Cocos2d-x 3.0版本已经发布了rc2,这让这段时间用熟了Cocos2d-x 2.2.2的我也有些蠢蠢欲动。按照触控科技主创人员在CocoaChina2014大会上的讲解,Cocos2d-x 3.0版本相比2.x版本在各方面都有不错的提升,于是乎就想把手头上的一款习作移植到3.0版本引擎下,看看运行效果如何。不过在移植之前,我先来看看 3.0与2.0相比在整体代码结构以及引擎驱动核心方面到底有哪些变化。一旦搞定这些原理,迁移什么都不是问题了。这里以Cocos2d-x 3.0rc0版的Android平台引擎为例。
一、从NativeActivity开始
Cocos2d-x 2.x版本中,游戏的Main Activity继承于引擎实现的Cocos2dxActivity(见《Hello,Cocos2d-x》),Cocos2dxActivity将一个 GLSurfaceView实例set给Window对象,并在为GLSurfaceView设置Renderer实例时创建Renderer Thread(渲染线程)。而Java代码则通过jni将游戏引擎中的C++代码引入。
Cocos2d-x 3.0版本则进一步摆脱Java束缚,当然也有新渲染器设计方面的考虑,3.0版本直接使用了Android NDK中提供的NativeActivity,我们游戏的主Activity(比如cpp-empty-test中的Cocos2dxActivity) 直接从NativeActivity继承:
// tests/cpp-empty-test/proj.android/src/org/cocos2dx/cpp_empty_test/Cocos2dxActivity.java
	public class Cocos2dxActivity extends NativeActivity {
    @Override
	    protected void onCreate(Bundle savedInstanceState) {
	        // TODO Auto-generated method stub
	        super.onCreate(savedInstanceState);
        … …
        //2.Set the format of window
	        // getWindow().setFormat(PixelFormat.TRANSLUCENT);
    }
	}
可以看出这里的Cocos2dxActivity啥也没做,因此整个Cocos2d-x 3.0游戏的起点就应该是NativeActivity了。
NativeActivity是Android专为使用NDK开发Android应用而实现的一个Activity类,通过使用 NativeActivity,开发者可以最大程度地摆脱Java代码的编写,尽可能的使用C++代码。
二、NativeActivity的原理
NativeActivity在Android 2.3版本引入,其核心方法依旧是onCreate,我们一起来看一下(省略部分与分析无关的代码):
// NativeActivity.java(Android  4.4.2_r1)
@Override
	    protected void onCreate(Bundle savedInstanceState) {
	        String libname = "main";
	        String funcname = "ANativeActivity_onCreate";
	        ActivityInfo ai;
	        … …
        mNativeContentView = new NativeContentView(this);
	        mNativeContentView.mActivity = this;
	        setContentView(mNativeContentView);
	        mNativeContentView.requestFocus();
	        mNativeContentView.getViewTreeObserver()
	                          .addOnGlobalLayoutListener(this);
	       
	        try {
	            ai = getPackageManager().getActivityInfo(
	                    getIntent().getComponent(),
	                    PackageManager.GET_META_DATA);
	            if (ai.metaData != null) {
	                String ln = ai.metaData.getString(META_DATA_LIB_NAME);
	                if (ln != null) libname = ln;
	                ln = ai.metaData.getString(META_DATA_FUNC_NAME);
	                if (ln != null) funcname = ln;
	            }
	        } catch (PackageManager.NameNotFoundException e) {
	            throw new RuntimeException("Error getting activity info", e);
	        }
	       
	        String path = null;
	       
	        File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
	                System.mapLibraryName(libname));
	        if (libraryFile.exists()) {
	            path = libraryFile.getPath();
	        }
	       
	        if (path == null) {
	            throw new IllegalArgumentException(
	                      "Unable to find native library: " + libname);
	        }
	       
	        byte[] nativeSavedState = savedInstanceState != null
	          ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
        mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
	                getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()),
	                getAbsolutePath(getExternalFilesDir(null)),
	                Build.VERSION.SDK_INT, getAssets(), nativeSavedState);
        if (mNativeHandle == 0) {
	            throw new IllegalArgumentException("Unable to load native library: "
	                      + path);
	        }
	        super.onCreate(savedInstanceState);
	    }
从NativeActivity的onCreate代码我们大致可以看出,NativeActivity在游戏对应的原生库(.so)中查找名为"ANativeActivity_onCreate"的函数,并执行该函数。其执行 是通过loadNativeCode这个Jni方法实现的。loadNativeCode在 /core/jni/android_app_NativeActivity.cpp中有实现:
 /core/jni/android_app_NativeActivity.cpp
	static jint
	loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName,
	        jobject messageQueue,
	        jstring internalDataDir, jstring externalDataDir, int sdkVersion,
	        jobject jAssetMgr, jbyteArray savedState)
	{
	    LOG_TRACE("loadNativeCode_native");
	    const char* pathStr = env->GetStringUTFChars(path, NULL);
	    NativeCode* code = NULL;
	    void* handle = dlopen(pathStr, RTLD_LAZY);
	    env->ReleaseStringUTFChars(path, pathStr);
	    if (handle != NULL) {
	        const char* funcStr = env->GetStringUTFChars(funcName, NULL);
	        code = new NativeCode(handle, (ANativeActivity_createFunc*)
	                dlsym(handle, funcStr));
	        env->ReleaseStringUTFChars(funcName, funcStr);
	        if (code->createActivityFunc == NULL) {
	            LOGW("ANativeActivity_onCreate not found");
	            delete code;
	            return 0;
	        }
	        … …
	        code->createActivityFunc(code, rawSavedState, rawSavedSize);
	        if (rawSavedState != NULL) {
	            env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
	        }
	    }
	    return (jint)code;
	}
做过系统编程的朋友想必对dlsym都很熟系,这个函数用来从一个打开的.so中(dlopen)获得某个函数对应的代码地址。 code->createActivityFunc则是执行这个函数。
我们在强化一下,费了半天劲儿找到并执行的这个函数的名字是:ANativeActivity_onCreate。 如果你要使用NativeActivity,你就必须提供一份ANativeActivity_onCreate函数的实现。在该函数的实现中, 你要为Activity注册各种生命周期事件以及其他输入事件的回调函数,比如onStart、onResume、onDestroy等。NDK 官方文档中有详细的说明。
不过这样一来,所有的事件处理均在NativeActivity所在的主线程里执行,为了不阻塞主线程的页面刷新以及交互响应,我们需要将这些回 调函数实现的短小精悍,不能拖泥带水,不能“干重活儿”。以前使用SDK时,Android SDK提供了AsyncTask, Handler, Runnable, Thread等诸多手段帮助在后台处理一些“重量级”的事情,但在NDK中,我们该如何处理呢?NDK也为我们提供了一种方案: android_native_app_glue。
android_native_app_glue大致做了这么几件事:
	1、实现了ANativeActivity_onCreate函数,注册了 Callback函数;
	2、创建一个新的子Thread,用于干重活儿
	3、在Main Thread和新线程之间建立了一个管道,用于Main Thread给新线程传递各种事件,以便后者读取并处理。
可以说native_app_glue的存在,进一步降低了 NativeActivity的使用门槛,否则以上诸事均要有开发人员自行实现。
下面结合源码做简单说明:
// android-ndk-r9c/sources/android/native_app_glue/android_native_app_glue.c
void ANativeActivity_onCreate(ANativeActivity* activity,
	        void* savedState, size_t savedStateSize) {
	    LOGV("Creating: %p\n", activity);
	    activity->callbacks->onDestroy = onDestroy;
	    activity->callbacks->onStart = onStart;
	    activity->callbacks->onResume = onResume;
	    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
	    activity->callbacks->onPause = onPause;
	    activity->callbacks->onStop = onStop;
	    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
	    activity->callbacks->onLowMemory = onLowMemory;
	    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
	    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
	    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
	    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
	    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
    activity->instance = android_app_create(activity, savedState, savedStateSize);
	}
static struct android_app* android_app_create(ANativeActivity* activity,
	        void* savedState, size_t savedStateSize) {
	    struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));
	    memset(android_app, 0, sizeof(struct android_app));
	    android_app->activity = activity;
    pthread_mutex_init(&android_app->mutex, NULL);
	    pthread_cond_init(&android_app->cond, NULL);
    … …
	    int msgpipe[2];
	    if (pipe(msgpipe)) {
	        LOGE("could not create pipe: %s", strerror(errno));
	        return NULL;
	    }
	    android_app->msgread = msgpipe[0];
	    android_app->msgwrite = msgpipe[1];
    pthread_attr_t attr;
	    pthread_attr_init(&attr);
	    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	    pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
    // Wait for thread to start.
	    pthread_mutex_lock(&android_app->mutex);
	    while (!android_app->running) {
	        pthread_cond_wait(&android_app->cond, &android_app->mutex);
	    }
	    pthread_mutex_unlock(&android_app->mutex);
    return android_app;
	}
上面的android_app_create创建了子线程,建立了两个线程的pipe,新线程的入口是android_app_entry:
static void* android_app_entry(void* param) {
	    struct android_app* android_app = (struct android_app*)param;
    android_app->config = AConfiguration_new();
	    AConfiguration_fromAssetManager(android_app->config,
	             android_app->activity->assetManager);
    print_cur_config(android_app);
    android_app->cmdPollSource.id = LOOPER_ID_MAIN;
	    android_app->cmdPollSource.app = android_app;
	    android_app->cmdPollSource.process = process_cmd;
	    android_app->inputPollSource.id = LOOPER_ID_INPUT;
	    android_app->inputPollSource.app = android_app;
	    android_app->inputPollSource.process = process_input;
    ALooper* looper = ALooper_prepare(
	                      ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
	    ALooper_addFd(looper, android_app->msgread,
	                  LOOPER_ID_MAIN,
	                  ALOOPER_EVENT_INPUT, NULL,
	                  &android_app->cmdPollSource);
	    android_app->looper = looper;
    pthread_mutex_lock(&android_app->mutex);
	    android_app->running = 1;
	    pthread_cond_broadcast(&android_app->cond);
	    pthread_mutex_unlock(&android_app->mutex);
    android_main(android_app);
    android_app_destroy(android_app);
	    return NULL;
	}
新线程建立了事件处理设施(looper),并通知主线程(通过条件变量)app正式开始运行了(running = 1),之后进入android_main。
Cocos2d-x 3.0采用的就是android_native_app_glue这 种方案,而android_main则是Cocos2d-x 3.0引擎层的入口。
//cocos/2d/platform/android/Android.mk
LOCAL_WHOLE_STATIC_LIBRARIES    := android_native_app_glue cocos_png_static cocos_jpeg_static cocos_tiff_static cocos_webp_static
	$(call import-module,android/native_app_glue)
三、走进引擎
从android_main函数开始,我们就进入了Cocos2d-x 3.0引擎的范畴。android_main函数比较长,我们挑重点说:
cocos/2d/platform/android/nativeactivity.cpp
void android_main(struct android_app* state) {
	    … …
    memset(&engine, 0, sizeof(engine));
	    state->userData = &engine;
	    state->onAppCmd = engine_handle_cmd;
	    state->onInputEvent = engine_handle_input;
	    state->inputPollSource.process = process_input;
	    engine.app = state;
    // Prepare to monitor accelerometer
	    … …
    while (1) {
	        // Read all pending events.
	        int ident;
	        int events;
	        struct android_poll_source* source;
        // If not animating, we will block forever waiting for events.
	        // If animating, we loop until all events are read, then continue
	        // to draw the next frame of animation.
	        while ((ident=ALooper_pollAll(engine.animating ? 0 : -1,
	                NULL, &events,
	                (void**)&source)) >= 0) {
            // Process this event.
	            if (source != NULL) {
	                source->process(state, source);
	            }
	            … …
	            // Check if we are exiting.
	            if (state->destroyRequested != 0) {
	                engine_term_display(&engine);
                memset(&engine, 0, sizeof(engine));
	                s_methodInitialized = false;
	                return;
	            }
	        }
        if (engine.animating) {
	            // Done with events; draw next animation frame.
	            engine.state.angle += .01f;
	            if (engine.state.angle > 1) {
	                engine.state.angle = 0;
	            }
            // Drawing is throttled to the screen update rate, so there
	            // is no need to do timing here.
	            LOG_RENDER_DEBUG("android_main : engine.animating");
	            engine_draw_frame(&engine);
	        } else {
	            LOG_RENDER_DEBUG("android_main : !engine.animating");
	        }
	        …
	    }
	}
android_main有些像cocos2d-x 2.2.2中GLThread的guardedRun方法,里面基本上就是一个死循环(while (1)),简化后的逻辑大致如下:
void android_main(struct android_app* state) {
	    while (1) {
	        Do Main Thread Event Processing & Input Event Processing;
	        if (engine.animating) {
	            // draw next animation frame 画下一帧
	            engine_draw_frame(&engine);
	        }
	    }
	}
而引擎的初始化和帧渲染就是在这个死循环中一步步完成的。
引擎的初始化始于APP_CMD_INIT_WINDOW事件,在engine_handle_cmd中,我们可以看到:
static void engine_handle_cmd(struct android_app* app, int32_t cmd)
	{
	    struct engine* engine = (struct engine*)app->userData;
	    switch (cmd) {
	        … …
	        case APP_CMD_INIT_WINDOW:
	            // The window is being shown, get it ready.
	            if (engine->app->window != NULL) {
	                cocos_dimensions d = engine_init_display(engine);
	                if ((d.w > 0) &&
	                    (d.h > 0)) {
	                    cocos2d::JniHelper::setJavaVM(app->activity->vm);
	                    cocos2d::JniHelper::setClassLoaderFrom(app->activity->clazz);
                    // call Cocos2dxHelper.init()
	                    cocos2d::JniMethodInfo ccxhelperInit;
	                    if (!cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit,
	                                             "org/cocos2dx/lib/Cocos2dxHelper",
	                                             "init",
	                                             "(Landroid/app/Activity;)V")) {
	                        LOGI("cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit) FAILED");
	                    }
	                    ccxhelperInit.env->CallStaticVoidMethod(ccxhelperInit.classID,
	                                                            ccxhelperInit.methodID,
	                                                            app->activity->clazz);
                    cocos_init(d, app);
	                }
	                engine->animating = 1;
	                engine_draw_frame(engine);
	            }
	            break;
	           … …
	    }
	}
当收到主线程通知的窗口建立事件时,engine_handle_cmd的APP_CMD_INIT_WINDOW事件处理函数主要做了两件事:
	1、调用engine_init_display初始化EGL;
	2、调用cocos_init初始化引擎的主要角色。
这里进入到引擎初始化的前提是“engine->app->window != NULL”。而app->window的设置是在native_app_glue中进行的,大致流程是:
	Main Thread:
	onNativeWindowCreated
	    -> android_app_set_window
	    -> android_app->pendingWindow = window;
	    -> android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
Sub Thread:
	process_cmd
	    -> android_app_pre_exec_cmd
	        -> android_app->window = android_app->pendingWindow;
	        -> engine_handle_cmd(即app->onAppCmd回调),此时android_app->window != NULL。
四、引擎初始化
前面说过,引擎初始化包括两部分:engine_init_display和cocos_init,我们分别来说说。
1、engine_init_display
//cocos/2d/platform/android/nativeactivity.cpp
static cocos_dimensions engine_init_display(struct engine* engine)
	{
	    cocos_dimensions r;
	    … …
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(display, 0, 0);
	    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
	    eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
    ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format);
    surface = eglCreateWindowSurface(display, config, engine->app->window, NULL);
    const EGLint eglContextAttrs[] =
	    {
	        EGL_CONTEXT_CLIENT_VERSION, 2,
	        EGL_NONE
	    };
    context = eglCreateContext(display, config, NULL, eglContextAttrs);
    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
	        LOGW("Unable to eglMakeCurrent");
	        return r;
	    }
    eglQuerySurface(display, surface, EGL_WIDTH, &w);
	    eglQuerySurface(display, surface, EGL_HEIGHT, &h);
    engine->display = display;
	    engine->context = context;
	    engine->surface = surface;
	    engine->width = w;
	    engine->height = h;
	    engine->state.angle = 0;
    r.w = w;
	    r.h = h;
    return r;
	}
这段代码应该是典型的EGL初始化流程,几乎每本有关EGL或opengl es的教程中都会有类似描述。个人对opengl以及EGL了解不多,从一些书籍或网络资料中大致得到如下一些理解:
首先,Android下每个Activity都会有对应窗口(Window)以及View,View就是显示在屏幕上的内容。NativeActivity初始化时设置了一个NativeContentView(View的子类):
// android/app/NativeActivity.java
static class NativeContentView extends View {
	        NativeActivity mActivity;
        public NativeContentView(Context context) {
	            super(context);
	        }
        public NativeContentView(Context context,
	                           AttributeSet attrs) {
	            super(context, attrs);
	        }
	    }
protected void onCreate(Bundle savedInstanceState) {
	        … …
	        mNativeContentView = new NativeContentView(this);
	        mNativeContentView.mActivity = this;
	        setContentView(mNativeContentView);
	        … …
	}
但Cocos2d-x显然不会使用这个View,而是直接在窗口用opengl绘图。EGL大致有三个元素:Context、Display以及 Surface。它们之间的大致关系是:EGL通过Context指挥opengl在Surface画布(一种帧缓冲FrameBuffer)上绘制,绘 制完成后再Swap到窗口的Display显示器上去,这样我们就能看到绘制的图像了。2D游戏引擎的渲染器用的都是这个原理。关于上述EGL初始化的具 体调用含义这里就不赘述了,大家如要深入了解,可以找本OpenGL ES相关的书去看看。
2、cocos_init
static void cocos_init(cocos_dimensions d, struct android_app* app)
	{
	    LOGI("cocos_init(…)");
	    pthread_t thisthread = pthread_self();
	    LOGI("pthread_self() = %X", thisthread);
    cocos2d::FileUtilsAndroid::setassetmanager(app->activity->assetManager);
    auto director = cocos2d::Director::getInstance();
	    auto glview = director->getOpenGLView();
	    if (!glview)
	    {
	        glview = cocos2d::GLView::create("Android app");
	        glview->setFrameSize(d.w, d.h);
	        director->setOpenGLView(glview);
        cocos_android_app_init(app);
        cocos2d::Application::getInstance()->run();
	    }
	    else
	    {
	        cocos2d::GL::invalidateStateCache();
	        cocos2d::ShaderCache::getInstance()->reloadDefaultShaders();
	        cocos2d::DrawPrimitives::init();
	        cocos2d::VolatileTextureMgr::reloadAllTextures();
        cocos2d::EventCustom foregroundEvent(EVENT_COME_TO_FOREGROUND);
	        director->getEventDispatcher()->dispatchEvent(&foregroundEvent);
	        director->setGLDefaultValues();
	    }
	}
分析过Cocos2d-x 2.x版本引擎结构的朋友对这段代码一定比较眼熟,没错,在2.x版本中这段代码是放在游戏项目的proj.android/jni/下的,在jni方法Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit中我们可以看到类似代码。
在cocos_init中我们看到了Cocos2d-x游戏引擎的一个重要角色Director的创建和初始化:
auto director = cocos2d::Director::getInstance();
//cocos/2d/CCDirector.cpp
bool Director::init(void)
	{
	    setDefaultValues();
    … …
    _openGLView = nullptr;
    _contentScaleFactor = 1.0f;
    // scheduler
	    _scheduler = new Scheduler();
	    // action manager
	    _actionManager = new ActionManager();
	    _scheduler->scheduleUpdate(_actionManager,
	                   Scheduler::PRIORITY_SYSTEM, false);
    _eventDispatcher = new EventDispatcher();
	    _eventAfterDraw = new EventCustom(EVENT_AFTER_DRAW);
	    _eventAfterDraw->setUserData(this);
	    _eventAfterVisit = new EventCustom(EVENT_AFTER_VISIT);
	    _eventAfterVisit->setUserData(this);
	    _eventAfterUpdate = new EventCustom(EVENT_AFTER_UPDATE);
	    _eventAfterUpdate->setUserData(this);
	    _eventProjectionChanged = new EventCustom(EVENT_PROJECTION_CHANGED);
	    _eventProjectionChanged->setUserData(this);
    //init TextureCache
	    initTextureCache();
    _renderer = new Renderer;
	    _console = new Console;
    return true;
	}
诸多引擎基础设施都是在Director::init中被初始化的,这里最重要的就是Renderer了,这个就是Cocos2d-x 3.0新实现的渲染器。
Director初始化后,_openGLView == NULL,后续cocos_init调用cocos2d::GLView::create("Android app")创建GLView,并set到Director中。这个View似乎更多是用来辅助处理屏幕适配以及触屏事件处理的。
cocos_init最后调用了cocos_android_app_init(app),这个函数实现在你的游戏工程中,以cpp-empty- test为例,在tests/cpp-empty-test/proj.android/jni/main.cpp中我们看到了该函数的实现:
void cocos_android_app_init (struct android_app* app) {
	    LOGD("cocos_android_app_init");
	    AppDelegate *pAppDelegate = new AppDelegate();
	}
我们已经进入游戏业务逻辑层了。和Cocos2d-x 2.x版本一样,Classes/AppDelegate.cpp中的 AppDelegate::applicationDidFinishLaunching依旧是我们初始化我们游戏业务逻辑层的入口。而这一入口函数是在 cocos_init中的cocos2d::Application::getInstance()->run()调用时被调用的。
// /cocos/2d/platform/android/CCApplication.cpp
	int Application::run()
	{
	    // Initialize instance and cocos2d.
	    if (! applicationDidFinishLaunching())
	    {
	        return 0;
	    }
    return -1;
	}
至此,我们又回到了熟悉的游戏业务逻辑层,也就是你的游戏project中。
五、到底发生了哪些重要变化
之前听说Cocos2d-x 3.0引擎的一个重要改造就是尽可能利用多线程,利用硬件的多核来提升游戏渲染性能。这给我的错觉是Renderer Thread完全独立出去,只负责渲染。但实际发布的版本似乎并不是这么回事。Cocos2d-x 2.x版本是两个线程,3.0版本依旧是两个线程,从cpp-empty-test运行的logcat日志也能看出来:
04-21 07:36:52.779  1522  1522 D dalvikvm: Late-enabling CheckJNI
	04-21 07:36:52.783  1522  1522 I dalvikvm: Enabling JNI app bug workarounds for target SDK version 9…
	04-21 07:36:52.783   561   573 I ActivityManager: Start proc org.cocos2dx.cpp_empty_test for activity org.cocos2dx.cpp_empty_test/.            Cocos2dxActivity: pid=1522 uid=10056 gids={50056}
	04-21 07:36:53.047  1522  1535 D libEGL  : loaded /system/lib/egl/libEGL_genymotion.so
	04-21 07:36:53.047  1522  1535 D         : HostConnection::get() New Host Connection established 0xb918a7c8, tid 1535
	04-21 07:36:53.071  1522  1535 D libEGL  : loaded /system/lib/egl/libGLESv1_CM_genymotion.so
	04-21 07:36:53.083  1522  1535 D libEGL  : loaded /system/lib/egl/libGLESv2_genymotion.so
	04-21 07:36:53.143  1522  1535 D JniHelper: JniHelper::setJavaVM(0xb903a730), pthread_self() = B9180250
	04-21 07:36:53.155  1522  1535 I cocos2dx/nativeactivity.cpp: cocos_init(…)
	… …
	04-21 07:36:53.395  1522  1535 D main    : cocos_android_app_init
	04-21 07:36:53.419  1522  1535 D CCFileUtilsAndroid.cpp: relative path = ipadhd/CloseNormal.png
	04-21 07:36:53.427  1522  1535 D CCFileUtilsAndroid.cpp: relative path = ipadhd/CloseSelected.png
	04-21 07:36:53.439  1522  1535 D cocos2d-x debug info: cocos2d: fullPathForFilename: No file found at Arial. Possible missing file.
	04-21 07:36:53.447  1522  1535 D dalvikvm: GC_FOR_ALLOC freed 64K, 4% free 3455K/3584K, paused 7ms, total 7ms
	04-21 07:36:53.467  1522  1535 D CCFileUtilsAndroid.cpp: relative path = ipadhd/HelloWorld.png
	04-21 07:36:54.003  1522  1535 I cocos2dx/nativeactivity.cpp: engine_draw_frame(…)
	04-21 07:36:54.003  1522  1535 I cocos2dx/nativeactivity.cpp: pthread_self() = B9180250
	04-21 07:36:54.003  1522  1535 I cocos2dx/nativeactivity.cpp: engine_draw_frame : just called cocos' mainLoop()
	04-21 07:36:54.051  1522  1535 I cocos2dx/nativeactivity.cpp: android_main : engine.animating
	04-21 07:36:54.051  1522  1535 I cocos2dx/nativeactivity.cpp: engine_draw_frame(…)
	04-21 07:36:54.051  1522  1535 I cocos2dx/nativeactivity.cpp: pthread_self() = B9180250
	… …
	04-21 07:36:56.507  1522  1535 I Process : Sending signal. PID: 1522 SIG: 9
可以看出NativeActivity所在的主线程号为1522,但绝大多数工作都在1535这个渲染线程,也就是native_app_glue库中创 建的那个线程。Scene Graph管理和Renderer::render依旧都在该Thread内完成,这似乎也很难有效并充分的利用起多核的效能啊。cpp-empty- test在我的genymotion模拟器上跑时,帧数始终在50帧左右。
不过Renderer的确是重写的,并且将2.x版本中Scene Graph的管理与渲染之间的耦合解耦开来。每帧按Scene Graph Visit Node时并不真正执行渲染,而只是构造DrawCommand,并插入到Renderer的DrawCommand队列中:
// draw
void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
	{
	    // Don't do calculate the culling if the transform was not updated
	    _insideBounds = transformUpdated ? isInsideBounds() : _insideBounds;
    if(_insideBounds)
	    {
	        _quadCommand.init(_globalZOrder, _texture->getName(), _shaderProgram, _blendFunc, &_quad, 1, transform);
	        renderer->addCommand(&_quadCommand);
	#if CC_SPRITE_DEBUG_DRAW
	        _customDebugDrawCommand.init(_globalZOrder);
	        _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
	        renderer->addCommand(&_customDebugDrawCommand);
	#endif //CC_SPRITE_DEBUG_DRAW
	    }
	}
在Director::drawScene尾部我们能看到真正的渲染动作render()被调用:
void Director::drawScene()
	{
	    … …
	    // draw the scene
	    if (_runningScene)
	    {
	        _runningScene->visit(_renderer, identity, false);
	        _eventDispatcher->dispatchEvent(_eventAfterVisit);
	    }
    _renderer->render();
	    _eventDispatcher->dispatchEvent(_eventAfterDraw);
    kmGLPopMatrix();
    _totalFrames++;
    // swap buffers
	    if (_openGLView)
	    {
	        _openGLView->swapBuffers();
	    }
    if (_displayStats)
	    {
	        calculateMPF();
	    }
	}
这里我们看到了 _openGLView->swapBuffers(),但该方法的具体实现为空,真正swapBuffers调用在外层:
static void engine_draw_frame(struct engine* engine)
	{
	    LOG_RENDER_DEBUG("engine_draw_frame(…)");
	    pthread_t thisthread = pthread_self();
	    LOG_RENDER_DEBUG("pthread_self() = %X", thisthread);
    if (engine->display == NULL) {
	        // No display.
	        LOGW("engine_draw_frame : No display.");
	        return;
	    }
    dispatch_pending_runnables();
	    cocos2d::Director::getInstance()->mainLoop();
	    LOG_RENDER_DEBUG("engine_draw_frame : just called cocos' mainLoop()");
    /* // Just fill the screen with a color. */
	    /* glClearColor(((float)engine->state.x)/engine->width, engine->state.angle, */
	    /*         ((float)engine->state.y)/engine->height, 1); */
	    /* glClear(GL_COLOR_BUFFER_BIT); */
    if (s_pfEditTextCallback && editboxText)
	    {
	        s_pfEditTextCallback(editboxText, s_ctx);
	        free(editboxText);
	        editboxText = NULL;
	    }
    eglSwapBuffers(engine->display, engine->surface);
	}
还记得android_main中的“死循环”么?那个死循环在每一帧都会调用engine_draw_frame方法,而这恰是整个Cocos2d-x 3.0引擎的驱动中心。
通过汇集各个Node的DrawCommand而不是直接Draw,新渲染器可以做一些优化,比如Batch Renderer等。这在上一版本引擎中较难实现,或者只能显式的通过CCSpriteBatchNode实现。更多的好处可以参考官方说明,或待日后使 用引擎时挖掘。
六、其他
Cocos2d-x 3.0引擎的C++部分采用了C++ 11标准中的语法,因此如果你要编译Linux版本游戏,你需要升级你的gcc编译器到4.7以上版本。但如果只构建Android 游戏,Android NDK(r9c以后版本)早为我们准备好了arm和x86平台的4.8版本的g++编译器了。
Cocos2d-x 3.0的内存管理依旧沿用内存计数机制,如果你理解了2.x版本的内存管理,理解3.0版本应该不会有太大问题。
七、参考资料
	 
	  – Android NDK源码(r9c)
	  -  Cocos2d-x 3.0rc1源码
	  – Android SDK源码(4.4.2_r1)
	  – 《Android Native Development Kit Cookbook — A step-by-step tutorial with more than 60 concise recipes on Android NDK development skills》。
                 
            
评论