标签 思考 下的文章

Hello, Cocos2d-x 3.0rc0

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》。

说说执行力

You are never to dictate what I can and can not do. The only two words I want to hear from you when I ask you to do something are "Yes" and "Sir"。(我能做什么不能做什么,你管不着。我吩咐你做事的时候,只想听到两个词,"是的"和"先生"。)
                                                                                   – 《纸牌屋》第一季

想必大家都基本认同:最有执行力的团队莫过于军队。军队有纪律约束,有荣誉感引导。在战时,违抗命令者是可以被直接拉出去枪毙的(影视剧中^_^)。但在 现实中你的团队里,你行么?别说枪毙,说了一句刺耳的批评的话,都可能招来各种不合作。大多数工作并非金饭碗,Google , FB的员工还经常跳来跳去呢,“此处不留爷,自有留爷处”才是硬道理。

那我们靠什么保证团队执行力呢?

靠NB人士?也许可行。但看看你的荷包,金子够么?NB人士属于金字塔顶端,数量稀少,价格昂贵,且多聚集在国内外知名名企。对于普通企业来说,操作起来有极大困难。
大把金钱奖励?我们不是土豪,没有挥金如土的气魄,钱不是不可以给,但要用在刀刃上。
精神鼓励?可以用,但不能一直用,否则下面就要说你是“精神病”了。

回归本源,执行力是建立在员工的职业操守上的。在这样一个前提下,我们至少要做好三件事来保证执行力。

1、明确责任
让员工确定、一定以及肯定的明确这件事/这些事的责任人就是他/她。做好了获奖,做少了、做错了、做砸了,他就是“罪魁祸首”。

2、设立检查点
为了帮助员工工作在正确的方向上,不走偏,不做错,不漏做,我们要帮助员工建立好检查点,明确告知检查点所有内容。在检查点上有针对性的检查也是员工的责任之一。

3、频繁反馈
团队负责人应该像老妈子似的不断的催促和获取反馈,以即时把握执行力的真实情况。

锡恩的4R执行力理论还强调了一个“即时奖励”,强化任务执行与后果之间的关系,让员工第一时间感受到良好执行带来的成果,获得成就感和认同。

接下来就要将以上三件事耐心的变成制度、变成流程,耐心地让员工按流程要求工作而不是按负责人的指令行事。

最后将以上流程自动化。通过系统分配任务,通过任务单将任务相关的信息整合在一起,为执行者提供帮助。通过这样的一个系统排除管理者的人肉提醒、减少在以 上环节中的人为疏漏(比如缺少检查点,忘记反馈),尽可能排除人的惰性、忘性等带来的种种执行问题。通过系统为任务执行作出评价,可作为员工绩效的参考。

通过以上的措施,还可以很好的应对“熟人文化”:不是按某人的指令,而是“制度规程”去做事,按照执行力系统的分配、提醒和通知去执行、去反馈。

这样一来,管理者也可以从繁重的“人肉管理”中脱离出来,既可以通过系统众览全局进度,保证任务的按时正确执行,也可以将精力更多的倾注在其他重要方面的工作中。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! 图片广告位1 图片广告位2 图片广告位3 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats