/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ //BEGIN_INCLUDE(all) #include #include #include #include #include #include #include #include #include #include #include // for prctl( PR_SET_NAME ) #include #include "pch.h" #include "app.h" #include "asset.h" #include "keymap.h" #include "main.h" #include "com_omixlab_panopainter_MainActivity.h" #ifdef __QUEST__ #include "oculus_vr.h" #elif __FOCUS__ #include "wave_vr.h" #endif typedef void (*GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); typedef void (*fnDebugMessageCallback)(GLDEBUGPROC callback, const void* userParam); #define GL_DEBUG_SEVERITY_HIGH 0x9146 #define GL_DEBUG_SEVERITY_MEDIUM 0x9147 #define GL_DEBUG_SEVERITY_LOW 0x9148 #define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B #define GL_DEBUG_OUTPUT 0x92E0 #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 EGLDisplay g_display = EGL_NO_DISPLAY; EGLContext g_context = EGL_NO_CONTEXT; std::recursive_mutex mutex; int mutex_count = 0; struct engine g_engine; thread_local JNIEnv* jni; jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { LOG("JNI_OnLoad"); return JNI_VERSION_1_6; } std::string utf8chr(int cp) { char c[5]={ 0x00,0x00,0x00,0x00,0x00 }; if (cp<=0x7F) { c[0] = cp; } else if(cp<=0x7FF) { c[0] = (cp>>6)+192; c[1] = (cp&63)+128; } else if(0xd800<=cp && cp<=0xdfff) {} //invalid block of utf8 else if(cp<=0xFFFF) { c[0] = (cp>>12)+224; c[1]= ((cp>>6)&63)+128; c[2]=(cp&63)+128; } else if(cp<=0x10FFFF) { c[0] = (cp>>18)+240; c[1] = ((cp>>12)&63)+128; c[2] = ((cp>>6)&63)+128; c[3]=(cp&63)+128; } return std::string(c); } // see https://stackoverflow.com/questions/21124051/receive-complete-android-unicode-input-in-c-c // see http://www.zedwood.com/article/cpp-utf8-char-to-codepoint int GetUnicodeChar(int eventType, int keyCode, int metaState) { #define COMBINING_ACCENT 0x80000000 #define COMBINING_ACCENT_MASK 0x7fffffff jclass class_key_event = jni->FindClass("android/view/KeyEvent"); int unicodeKey; if(metaState == 0) { jmethodID method_get_unicode_char = jni->GetMethodID(class_key_event, "getUnicodeChar", "()I"); jmethodID eventConstructor = jni->GetMethodID(class_key_event, "", "(II)V"); jobject eventObj = jni->NewObject(class_key_event, eventConstructor, eventType, keyCode); unicodeKey = jni->CallIntMethod(eventObj, method_get_unicode_char); } else { jmethodID method_get_unicode_char = jni->GetMethodID(class_key_event, "getUnicodeChar", "(I)I"); jmethodID eventConstructor = jni->GetMethodID(class_key_event, "", "(II)V"); jobject eventObj = jni->NewObject(class_key_event, eventConstructor, eventType, keyCode); unicodeKey = jni->CallIntMethod(eventObj, method_get_unicode_char, metaState); } if ((unicodeKey & COMBINING_ACCENT) != 0) { unicodeKey = unicodeKey & COMBINING_ACCENT_MASK; } LOG("Unicode key is: %d", unicodeKey); return unicodeKey; } void android_attach_jni() { g_engine.app->activity->vm->AttachCurrentThread(&jni, nullptr); } void android_detach_jni() { g_engine.app->activity->vm->DetachCurrentThread(); } void android_async_lock() { mutex.lock(); if (mutex_count == 0) eglMakeCurrent(g_engine.display, g_engine.surface, g_engine.surface, g_engine.context); mutex_count++; } bool android_async_trylock() { if (!mutex.try_lock()) return false; if (mutex_count == 0) eglMakeCurrent(g_engine.display, g_engine.surface, g_engine.surface, g_engine.context); mutex_count++; return true; } void android_async_swap() { eglSwapBuffers(g_engine.display, g_engine.surface); } void android_async_unlock() { mutex_count--; if (mutex_count == 0) eglMakeCurrent(g_engine.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); mutex.unlock(); } // see https://groups.google.com/forum/#!topic/android-ndk/Tk3g00wLKhk void displayKeyboard(bool pShow) { // Retrieves NativeActivity. jobject lNativeActivity = g_engine.app->activity->clazz; jclass ClassNativeActivity = jni->GetObjectClass(lNativeActivity); // Retrieves Context.INPUT_METHOD_SERVICE. jclass ClassContext = jni->FindClass("android/content/Context"); jfieldID FieldINPUT_METHOD_SERVICE = jni->GetStaticFieldID(ClassContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;"); jobject INPUT_METHOD_SERVICE = jni->GetStaticObjectField(ClassContext, FieldINPUT_METHOD_SERVICE); //jniCheck(INPUT_METHOD_SERVICE); // Runs getSystemService(Context.INPUT_METHOD_SERVICE). jclass ClassInputMethodManager = jni->FindClass( "android/view/inputmethod/InputMethodManager"); jmethodID MethodGetSystemService = jni->GetMethodID( ClassNativeActivity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); jobject lInputMethodManager = jni->CallObjectMethod( lNativeActivity, MethodGetSystemService, INPUT_METHOD_SERVICE); // Runs getWindow().getDecorView(). jmethodID MethodGetWindow = jni->GetMethodID( ClassNativeActivity, "getWindow", "()Landroid/view/Window;"); jobject lWindow = jni->CallObjectMethod(lNativeActivity, MethodGetWindow); jclass ClassWindow = jni->FindClass( "android/view/Window"); jmethodID MethodGetDecorView = jni->GetMethodID( ClassWindow, "getDecorView", "()Landroid/view/View;"); jobject lDecorView = jni->CallObjectMethod(lWindow, MethodGetDecorView); if (pShow) { // Runs lInputMethodManager.showSoftInput(...). jmethodID MethodShowSoftInput = jni->GetMethodID( ClassInputMethodManager, "showSoftInput", "(Landroid/view/View;I)Z"); jboolean lResult = jni->CallBooleanMethod( lInputMethodManager, MethodShowSoftInput, lDecorView, 0); } else { // Runs lWindow.getViewToken() jclass ClassView = jni->FindClass( "android/view/View"); jmethodID MethodGetWindowToken = jni->GetMethodID( ClassView, "getWindowToken", "()Landroid/os/IBinder;"); jobject lBinder = jni->CallObjectMethod(lDecorView, MethodGetWindowToken); // lInputMethodManager.hideSoftInput(...). jmethodID MethodHideSoftInput = jni->GetMethodID( ClassInputMethodManager, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z"); jboolean lRes = jni->CallBooleanMethod( lInputMethodManager, MethodHideSoftInput, lBinder, 0); } } /* * Class: com_omixlab_panopainter_MainActivity * Method: pickFileCallback * Signature: (Ljava/lang/String;)V */ std::function pick_file_callback; std::function pick_file_callback_context; extern "C" { #ifdef __FOCUS__ JNIEXPORT void JNICALL Java_com_omixlab_panopainter_MainActivity_init_vr(JNIEnv *env, jobject activity, jobject am) { Asset::m_am = AAssetManager_fromJava(env, am); wave_init(0, 0, 0); } #endif JNIEXPORT void JNICALL Java_com_omixlab_panopainter_MainActivity_pickFileCallback(JNIEnv *env, jobject, jstring path) { const char* path_utf = env->GetStringUTFChars(path, nullptr); std::string file_path = path_utf; // create a copy env->ReleaseStringUTFChars(path, path_utf); LOG("received %s", file_path.c_str()); pick_file_callback_context = [file_path] { if (pick_file_callback) { LOG("callback"); pick_file_callback(file_path); pick_file_callback = nullptr; } }; } JNIEXPORT void JNICALL Java_com_omixlab_panopainter_MainActivity_pickExternalCallback(JNIEnv *env, jobject, jstring path) { const char* path_utf = env->GetStringUTFChars(path, nullptr); std::string file_path = path_utf; // create a copy env->ReleaseStringUTFChars(path, path_utf); LOG("data_path %s", file_path.c_str()); App::I->data_path = file_path; App::I->work_path = file_path; App::I->rec_path = file_path + "/frames"; App::I->initLog(); } JNIEXPORT void JNICALL Java_com_omixlab_panopainter_MainActivity_contentRectChanged(JNIEnv *end, jobject, jint wnd_w, jint wnd_h, jint rect_left, jint rect_top, jint rect_right, jint rect_bottom) { #ifndef __QUEST__ LOG("resize wnd [%d %d] rect [%d %d %d %d]", wnd_w, wnd_h, rect_left, rect_top, rect_right, rect_bottom); App::I->width = wnd_w; App::I->height = (rect_bottom - rect_top); App::I->off_x = 0; App::I->off_y = wnd_h - (rect_bottom - rect_top); #endif } } void android_pick_file(std::function callback) { pick_file_callback = callback; jclass clazz = jni->GetObjectClass(g_engine.app->activity->clazz); jmethodID method = jni->GetMethodID(clazz, "pickFile", "()V"); jni->CallVoidMethod(g_engine.app->activity->clazz, method); } float get_display_density() { jclass clazz = jni->GetObjectClass(g_engine.app->activity->clazz); jmethodID method = jni->GetMethodID(clazz, "getDensity", "()F"); return jni->CallFloatMethod(g_engine.app->activity->clazz, method); } std::string get_data_path() { jclass clazz = jni->GetObjectClass(g_engine.app->activity->clazz); jmethodID method = jni->GetMethodID(clazz, "getDataPath", "()Ljava/lang/String;"); jstring js = (jstring)jni->CallObjectMethod(g_engine.app->activity->clazz, method); const char* utf = jni->GetStringUTFChars(js, nullptr); std::string str = utf; // create a copy jni->ReleaseStringUTFChars(js, utf); return str; } // source: https://github.com/opencollab/giws/issues/4 jstring JniStringFromUTF8(const std::string& s) { int len = s.size(); jbyteArray bytes = jni->NewByteArray(len); if (bytes == 0) { LOG("jni->NewByteArray failed"); return 0; } jni->SetByteArrayRegion(bytes, 0, len, (jbyte *)s.c_str()); jclass string_class = jni->FindClass("java/lang/String"); jmethodID stringConstructor = jni->GetMethodID(string_class, "", "([B)V" ); if (stringConstructor == NULL) { LOG("JniStringFromUTF8 new String(byte[]) failed"); jni->DeleteLocalRef(bytes); return 0; } jstring result = (jstring)jni->NewObject(string_class, stringConstructor, bytes); jni->DeleteLocalRef(bytes); return result; //NOTE: jstring must be freed using: curEnv->DeleteLocalRef(result); } std::string android_get_clipboard() { jclass clazz = jni->GetObjectClass(g_engine.app->activity->clazz); jmethodID method = jni->GetMethodID(clazz, "getClipboardText", "()Ljava/lang/String;"); jstring js = (jstring)jni->CallObjectMethod(g_engine.app->activity->clazz, method); const char* utf = jni->GetStringUTFChars(js, nullptr); std::string str = utf; // create a copy jni->ReleaseStringUTFChars(js, utf); return str; } bool android_set_clipboard(const std::string& s) { jclass clazz = jni->GetObjectClass(g_engine.app->activity->clazz); jmethodID method = jni->GetMethodID(clazz, "setClipboardText", "(Ljava/lang/String;)Z"); jstring js = JniStringFromUTF8(s); if (!js) return false; jboolean success = jni->CallBooleanMethod(g_engine.app->activity->clazz, method, js); jni->DeleteLocalRef(js); return success; } bool vr_running = false; static void engine_vr_loop() { LOG("start hmd render thread"); vr_running = true; App::I->ui_sync(); while (vr_running) { #ifdef __QUEST__ App::I->render_task([] { App::I->vr_draw_ui(); oculus_draw(0); }); #endif } LOG("hmd renderer terminated"); } static void engine_start_vr_thread() { std::thread(engine_vr_loop).detach(); } static void engine_start_vr_mode() { #ifdef __QUEST__ LOG("QUEST init VR"); oculus_init_vr(g_engine.display, g_engine.context, g_engine.app->window); #elif __FOCUS__ wave_init_vr(g_engine.display, g_engine.context, g_engine.app->window); #endif } /** * Initialize an EGL context for the current display. */ static int engine_init_display(struct engine* engine) { // initialize OpenGL ES and EGL /* * Here specify the attributes of the desired configuration. * Below, we select an EGLConfig with at least 8 bits per color * component compatible with on-screen windows */ #ifdef __QUEST__ EGLint egl_depth = 0; EGLint egl_alpha = 8; const EGLint attribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_SAMPLES, 0, EGL_NONE }; #else EGLint egl_depth = 24; EGLint egl_alpha = 0; const EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 0, EGL_SAMPLES, 0, EGL_NONE }; #endif EGLint w, h, format; EGLint numConfigs; EGLConfig config = nullptr; EGLSurface surface = nullptr; EGLContext context = nullptr; EGLDisplay display = g_display; if (g_display == EGL_NO_DISPLAY) { LOG("DYSPLAY CREATE"); display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, nullptr, nullptr); } else LOG("DISPLAY RESUME"); /* Here, the application chooses the configuration it desires. * find the best match if possible, otherwise use the very first one */ eglChooseConfig(display, attribs, nullptr,0, &numConfigs); std::unique_ptr supportedConfigs(new EGLConfig[numConfigs]); assert(supportedConfigs); eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs); assert(numConfigs); auto i = 0; for (; i < numConfigs; i++) { auto& cfg = supportedConfigs[i]; EGLint r, g, b, a, d; if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) && eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) && eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) && eglGetConfigAttrib(display, cfg, EGL_ALPHA_SIZE, &a) && eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) && r == 8 && g == 8 && b == 8 && a == egl_alpha && d == egl_depth ) { config = supportedConfigs[i]; break; } } if (i == numConfigs) { config = supportedConfigs[0]; } /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). * As soon as we picked a EGLConfig, we can safely reconfigure the * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); surface = eglCreateWindowSurface(display, config, engine->app->window, nullptr); #ifdef __QUEST__ const int gles_version = 3; #else const int gles_version = 2; #endif const EGLint attribs_test[] = { EGL_CONTEXT_CLIENT_VERSION, gles_version, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, EGL_NONE }; bool resuming_context = true; context = g_context; if (g_context == EGL_NO_CONTEXT) { LOG("CONTEXT CREATE"); context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test); resuming_context = false; } else LOG("CONTEXT RESUME"); if (context == EGL_NO_CONTEXT) { LOG("EGL: debug and forward context failed"); const EGLint attribs_test[] = { EGL_CONTEXT_CLIENT_VERSION, gles_version, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test); if (context == EGL_NO_CONTEXT) { LOG("EGL: only forward context failed"); const EGLint attribs_test[] = { EGL_CONTEXT_CLIENT_VERSION, gles_version, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, //EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test); if (context == EGL_NO_CONTEXT) { LOG("EGL: only debug context failed"); const EGLint attribs_test[] = { EGL_CONTEXT_CLIENT_VERSION, gles_version, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test); if (context == EGL_NO_CONTEXT) { LOG("EGL: all the context creation failed"); } else { LOG("EGL: created simple context"); } } else { LOG("EGL: created only debug context"); } } else { LOG("EGL: created only forward context"); } } else { LOG("EGL: created debug and forward context"); } if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { LOG("Unable to eglMakeCurrent"); return -1; } 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; g_display = display; g_context = context; if (resuming_context) { LOG("RESUME APP"); App::I->and_app = engine->app; LOG("release egl context"); eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); mutex.unlock(); engine_start_vr_mode(); return 0; } // Check openGL on the system auto opengl_info = { GL_VENDOR, GL_RENDERER, GL_VERSION/*, GL_EXTENSIONS*/ }; for (auto name : opengl_info) { auto info = glGetString(name); LOG("OpenGL Info: %s", info); } GLint n_exts; std::map ext_map; glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts); for (int i = 0; i < n_exts; i++) { ext_map.emplace((char*)glGetStringi(GL_EXTENSIONS, i), true); } //const char* ext = (const char*) glGetString(GL_EXTENSIONS); //int ext_len = strlen(ext); //static char ext_name[256]; //int ext_name_i = 0; //for (int i = 0; i < ext_len; i++) //{ // char c = ext[i]; // if (c == ' ') // { // ext_map.emplace(std::string(ext_name, ext_name_i), true); // ext_name_i = 0; // } // else // { // ext_name[ext_name_i++] = c; // } //} if (ext_map.count("GL_KHR_debug")) { LOG("GL_KHR_debug supported"); auto glDebugMessageCallback = (fnDebugMessageCallback)eglGetProcAddress("glDebugMessageCallbackKHR"); if (glDebugMessageCallback) { LOG("glDebugMessageCallback proc found at %p", glDebugMessageCallback); glDebugMessageCallback([](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { //if (severity == GL_DEBUG_SEVERITY_MEDIUM || severity == GL_DEBUG_SEVERITY_HIGH) { LOG("OPENGL: %.*s", length, message); } }, nullptr); glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } else { LOG("glDebugMessageCallback proc NOT FOUND"); } } FILE* file = popen("getprop", "r"); std::map os_props; if (file) { char output[100]; while (fgets(output, sizeof(output), file) != nullptr) { int i = 0; int l = strlen(output); char buf[64]; int j = 0; while (i < l && output[i] != '[') i++; i++; while (i < l && output[i] != ']') { buf[j++] = output[i]; i++; } std::string key(buf, j); j = 0; while (i < l && output[i] != '[') i++; i++; while (i < l && output[i] != ']') { buf[j++] = output[i]; i++; } os_props[key] = std::string(buf, j); //LOG("PROP: %s -> %s", key.c_str(), os_props[key].c_str()); } pclose(file); } LOG("PROP Android Version: %s", os_props["ro.build.version.release"].c_str()); LOG("PROP Android SDK: %s", os_props["ro.build.version.sdk"].c_str()); LOG("PROP Country Code: %s", os_props["ro.csc.country_code"].c_str()); LOG("PROP ABI: %s", os_props["ro.product.cpu.abilist"].c_str()); LOG("PROP Brand: %s", os_props["ro.product.brand"].c_str()); LOG("PROP Maker: %s", os_props["ro.product.manufacturer"].c_str()); LOG("PROP Mode: %s", os_props["ro.product.model"].c_str()); Asset::m_am = engine->app->activity->assetManager; App::I->and_app = engine->app; App::I->and_engine = engine; //std::string base_path = engine->app->activity->externalDataPath ? // engine->app->activity->externalDataPath : get_data_path(engine->app); if (App::I->data_path.empty() || App::I->data_path == ".") App::I->data_path = get_data_path(); LOG("data_path %s", App::I->data_path.c_str()); #ifdef __QUEST__ App::I->zoom = 1.f; App::I->width = 1024; App::I->height = 1024; App::I->redraw = true; App::I->vr_active = true; App::I->has_vr = true; App::I->vr_only = true; // give control to the render thread LOG("release egl context"); eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); LOG("start render threads"); App::I->render_thread_start(); LOG("start ui thread"); App::I->ui_thread_start(); LOG("start vr thread"); engine_start_vr_mode(); engine_start_vr_thread(); #else float density = get_display_density(); LOG("density %f", density); App::I->zoom = density / 1.5; App::I->width = w; App::I->height = h; App::I->redraw = true; // give control to the render thread LOG("release egl context"); eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); LOG("start render threads"); App::I->render_thread_start(); LOG("start ui thread"); App::I->ui_thread_start(); #endif return 0; } /** * Tear down the EGL context currently associated with the display. */ static void engine_term_display(struct engine* engine) { LOG("flush render thread"); App::I->render_sync(); mutex.lock(); if (engine->display != EGL_NO_DISPLAY) { eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); // if (engine->context != EGL_NO_CONTEXT) { // eglDestroyContext(engine->display, engine->context); // } if (engine->surface != EGL_NO_SURFACE) { eglDestroySurface(engine->display, engine->surface); } // eglTerminate(engine->display); } engine->animating = 0; engine->display = EGL_NO_DISPLAY; engine->context = EGL_NO_CONTEXT; engine->surface = EGL_NO_SURFACE; #ifdef __QUEST__ oculus_release_vr(); #endif } /** * Process the next input event. */ static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) { struct engine* engine = (struct engine*)app->userData; int32_t eventType = AInputEvent_getType(event); //LOG("event type: %d", eventType); //locker _locker{engine}; App::I->redraw = true; switch (eventType) { case AINPUT_EVENT_TYPE_MOTION: // switch (AInputEvent_getSource(event)) { // case AINPUT_SOURCE_STYLUS: // case AINPUT_SOURCE_TOUCHSCREEN: { int action = AKeyEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; int pointer_id = AMotionEvent_getPointerId(event, index); int32_t count = AMotionEvent_getPointerCount(event); auto findPointer = [](int id, AInputEvent* event) { int32_t count = AMotionEvent_getPointerCount(event); int ret = -1; for (int i = 0; i < count; i++) { //LOG("pointer %d id %d == %d", i, id, AMotionEvent_getPointerId(event, i)); if (AMotionEvent_getPointerId(event, i) == id) ret = i; } return ret; return -1; }; struct Pointer { int id = -1; int idx; glm::vec2 pos; }; static Pointer p0, p1; static int tracked = 0; //LOG("event source: %d", AInputEvent_getSource(event)); //LOG("pointer id %d count %d", pointer_id, count); MouseEvent e; switch (action) { case AMOTION_EVENT_ACTION_DOWN: { float x = AMotionEvent_getX(event, 0); float y = AMotionEvent_getY(event, 0); p0.id = AMotionEvent_getPointerId(event, 0); p0.pos = {x, y}; p0.idx = index; int tool_type = AMotionEvent_getToolType(event, index); float pressure = AMotionEvent_getPressure(event, 0); kEventSource source = tool_type == AMOTION_EVENT_TOOL_TYPE_STYLUS ? kEventSource::Stylus : kEventSource::Touch; App::I->ui_task_async([=]{ App::I->mouse_down(0, x, y, pressure, source, 0); }); tracked = 1; //LOG("first down"); return 1; } case AMOTION_EVENT_ACTION_POINTER_DOWN: { //LOG("pointer down index %d", index); if (count == 2) { float x = AMotionEvent_getX(event, 1); float y = AMotionEvent_getY(event, 1); p1.id = AMotionEvent_getPointerId(event, 1); p1.idx = index; p1.pos = {x, y}; p0.pos.x = AMotionEvent_getX(event, 0); p0.pos.y = AMotionEvent_getY(event, 0); //LOG("second down"); App::I->ui_task_async([t=tracked, _p0=p0, _p1=p1] { if (t == 1) App::I->mouse_cancel(0); App::I->gesture_start(_p0.pos, _p1.pos); }); tracked = 2; } return 1; } case AMOTION_EVENT_ACTION_UP: { float y = AMotionEvent_getY(event, 0); float x = AMotionEvent_getX(event, 0); p0.id = -1; p1.id = -1; int tool_type = AMotionEvent_getToolType(event, index); float pressure = AMotionEvent_getPressure(event, 0); kEventSource source = tool_type == AMOTION_EVENT_TOOL_TYPE_STYLUS ? kEventSource::Stylus : kEventSource::Touch; if (tracked == 1) { App::I->ui_task_async([=] { App::I->mouse_up(0, x, y, source, 0); }); } tracked = 0; //LOG("first up"); return 1; } case AMOTION_EVENT_ACTION_POINTER_UP: if (p1.id == AMotionEvent_getPointerId(event, 1)) { p1.id = -1; //LOG("second up"); App::I->ui_task_async([=] { App::I->gesture_end(); }); } return 1; case AMOTION_EVENT_ACTION_HOVER_MOVE: // pen move before touching { float y = AMotionEvent_getY(event, 0); float x = AMotionEvent_getX(event, 0); App::I->ui_task_async([=] { App::I->mouse_move(x, y, 0, kEventSource::Stylus, 0); }); //LOG("single move"); return 1; } case AMOTION_EVENT_ACTION_MOVE: if (count == 1 && tracked == 1) { float y = AMotionEvent_getY(event, 0); float x = AMotionEvent_getX(event, 0); int tool_type = AMotionEvent_getToolType(event, index); float pressure = AMotionEvent_getPressure(event, 0); kEventSource source = tool_type == AMOTION_EVENT_TOOL_TYPE_STYLUS ? kEventSource::Stylus : kEventSource::Touch; App::I->ui_task_async([=] { App::I->mouse_move(x, y, pressure, source, 0); }); //LOG("single move"); } else if (count == 2) { int idx = findPointer(pointer_id, event); //LOG("pointer move index %d", idx); if (p0.idx == idx) { //LOG("first move"); float y = AMotionEvent_getY(event, 0); float x = AMotionEvent_getX(event, 0); p0.pos = {x, y}; } if (p1.idx == idx) { //LOG("second move"); float x = AMotionEvent_getX(event, 1); float y = AMotionEvent_getY(event, 1); p1.pos = {x, y}; } App::I->ui_task_async([_p0=p0, _p1=p1] { App::I->gesture_move(_p0.pos, _p1.pos); }); } return 1; default: //LOG("motion action: %d", action); break; } } // break; // } // end switch break; case AINPUT_EVENT_TYPE_KEY: { int action = AKeyEvent_getAction(event); int32_t key_val = AKeyEvent_getKeyCode(event); int key = AKeyEvent_getKeyCode(event); int metaState = AKeyEvent_getMetaState(event); int uniValue = GetUnicodeChar(action, key, metaState); switch (action) { case AKEY_EVENT_ACTION_MULTIPLE: LOG("Received key multi event: %d\n", key_val); if (uniValue > 32 && uniValue < 127) //printable ascii range App::I->ui_task_async([=] { App::I->key_char(uniValue); }); break; case AKEY_EVENT_ACTION_DOWN: LOG("Received key down event: %d\n", key_val); App::I->ui_task_async([=] { App::I->key_down(convert_key(key_val)); }); break; case AKEY_EVENT_ACTION_UP: LOG("Received key up event: %d\n", key_val); App::I->ui_task_async([=] { App::I->key_up(convert_key(key_val)); if (uniValue > 32 && uniValue < 127) //printable ascii range App::I->key_char(uniValue); }); break; } return 1; } } // end switch return 0; } /** * Process the next main command. */ static void engine_handle_cmd(struct android_app* app, int32_t cmd) { struct engine* engine = (struct engine*)app->userData; switch (cmd) { case APP_CMD_RESUME: LOG("APP_CMD_RESUME"); App::I->redraw = true; ALooper_wake(engine->app->looper); break; case APP_CMD_SAVE_STATE: // The system has asked us to save our current state. Do so. engine->app->savedState = malloc(sizeof(struct saved_state)); *((struct saved_state*)engine->app->savedState) = engine->state; engine->app->savedStateSize = sizeof(struct saved_state); LOG("SAVE STATE"); break; case APP_CMD_INIT_WINDOW: // The window is being shown, get it ready. if (engine->app->window != NULL) engine_init_display(engine); break; case APP_CMD_TERM_WINDOW: // The window is being hidden or closed, clean it up. //App::I->terminate(); engine_term_display(engine); //exit(0); break; case APP_CMD_GAINED_FOCUS: /* // When our app gains focus, we start monitoring the accelerometer. if (engine->accelerometerSensor != NULL) { ASensorEventQueue_enableSensor(engine->sensorEventQueue, engine->accelerometerSensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(engine->sensorEventQueue, engine->accelerometerSensor, (1000L/60)*1000); } engine->animating = 1; */ break; case APP_CMD_LOST_FOCUS: /* // When our app loses focus, we stop monitoring the accelerometer. // This is to avoid consuming battery while not being used. if (engine->accelerometerSensor != NULL) { ASensorEventQueue_disableSensor(engine->sensorEventQueue, engine->accelerometerSensor); } // Also stop animating. engine->animating = 0; */ break; case APP_CMD_WINDOW_REDRAW_NEEDED: LOG("APP_CMD_WINDOW_REDRAW_NEEDED"); App::I->redraw = true; ALooper_wake(engine->app->looper); break; case APP_CMD_WINDOW_RESIZED: LOG("APP_CMD_WINDOW_RESIZED"); App::I->redraw = true; ALooper_wake(engine->app->looper); break; case APP_CMD_CONTENT_RECT_CHANGED: LOG("APP_CMD_CONTENT_RECT_CHANGED"); //App::I->width = engine->app->contentRect.right - engine->app->contentRect.left; //App::I->height = engine->app->contentRect.bottom - engine->app->contentRect.top; //LOG("content rect %f %f", App::I->width, App::I->height); App::I->redraw = true; ALooper_wake(engine->app->looper); break; } } /** * This is the main entry point of a native application that is using * android_native_app_glue. It runs in its own thread, with its own * event loop for receiving input events and doing other things. */ void android_main(struct android_app* state) { // Make sure glue isn't stripped. // DON'T REMOVE, even if the compiler say it's deprecated app_dummy(); App::I = new App; memset(&g_engine, 0, sizeof(g_engine)); state->userData = &g_engine; state->onAppCmd = engine_handle_cmd; state->onInputEvent = engine_handle_input; g_engine.app = state; // BEGIN OVR ANativeActivity_setWindowFlags(state->activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0 ); state->activity->vm->AttachCurrentThread(&jni, nullptr); #ifdef __QUEST__ oculus_init(state->activity->vm, jni, state->activity->clazz); #elif __FOCUS__ wave_init(state->activity->vm, jni, state->activity->clazz); #endif // Note that AttachCurrentThread will reset the thread name. prctl(PR_SET_NAME, (long)"PP Main", 0, 0, 0); // Prepare to monitor accelerometer /* g_engine.sensorManager = ASensorManager_getInstance(); g_engine.accelerometerSensor = ASensorManager_getDefaultSensor( g_engine.sensorManager, ASENSOR_TYPE_ACCELEROMETER); g_engine.sensorEventQueue = ASensorManager_createEventQueue( g_engine.sensorManager, state->looper, LOOPER_ID_USER, NULL, NULL); */ LOG("START MAIN"); if (state->savedState != NULL) { // We are starting with a previous saved state; restore from it. g_engine.state = *(struct saved_state*)state->savedState; } //App::I->create(); App::I->redraw = true; // loop waiting for stuff to do. 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. bool used = false; int timeout = g_engine.display != EGL_NO_DISPLAY ? 0 : -1; while (!used && (ident=ALooper_pollOnce(timeout, NULL, &events, (void**)&source)) != ALOOPER_POLL_ERROR) { // Process this event. // If a sensor has data, process it now. /* if (ident == LOOPER_ID_USER) { if (g_engine.accelerometerSensor != NULL) { ASensorEvent event; while (ASensorEventQueue_getEvents(g_engine.sensorEventQueue, &event, 1) > 0) { // LOGI("accelerometer: x=%f y=%f z=%f", // event.acceleration.x, event.acceleration.y, // event.acceleration.z); } } } */ if (source != NULL) { source->process(state, source); used = true; } if (g_engine.display == EGL_NO_DISPLAY || ident == ALOOPER_POLL_CALLBACK) continue; if (ident == ALOOPER_POLL_TIMEOUT) App::I->redraw = true; // Check if we are exiting. if (state->destroyRequested != 0) { engine_term_display(&g_engine); return; } } //if (engine.animating) if (g_engine.display != EGL_NO_DISPLAY) { if (pick_file_callback_context) { pick_file_callback_context(); pick_file_callback_context = nullptr; } } } } //END_INCLUDE(all)