Files
panopainter/android/src/main/cpp/main.cpp

1123 lines
38 KiB
C++
Executable File

/*
* 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 <mutex>
#include <initializer_list>
#include <memory>
#include <jni.h>
#include <errno.h>
#include <cassert>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "pch.h"
#include "app.h"
#include "asset.h"
#include "keymap.h"
#include "main.h"
#include "com_omixlab_panopainter_MainActivity.h"
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;
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(struct android_app* app, int eventType, int keyCode, int metaState)
{
#define COMBINING_ACCENT 0x80000000
#define COMBINING_ACCENT_MASK 0x7fffffff
JavaVM* javaVM = app->activity->vm;
JNIEnv* jniEnv = app->activity->env;
JavaVMAttachArgs attachArgs;
attachArgs.version = JNI_VERSION_1_6;
attachArgs.name = "NativeThread";
attachArgs.group = NULL;
jint result = javaVM->AttachCurrentThread(&jniEnv, &attachArgs);
if(result == JNI_ERR)
{
return 0;
}
jclass class_key_event = jniEnv->FindClass("android/view/KeyEvent");
int unicodeKey;
if(metaState == 0)
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "()I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char);
}
else
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "(I)I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char, metaState);
}
if ((unicodeKey & COMBINING_ACCENT) != 0)
{
unicodeKey = unicodeKey & COMBINING_ACCENT_MASK;
}
javaVM->DetachCurrentThread();
LOG("Unicode key is: %d", unicodeKey);
return unicodeKey;
}
void android_async_lock(struct engine* engine)
{
mutex.lock();
if (mutex_count == 0)
eglMakeCurrent(engine->display, engine->surface, engine->surface, engine->context);
mutex_count++;
}
bool android_async_trylock(struct engine* engine)
{
if (!mutex.try_lock())
return false;
if (mutex_count == 0)
eglMakeCurrent(engine->display, engine->surface, engine->surface, engine->context);
mutex_count++;
return true;
}
void android_async_swap(struct engine* engine)
{
eglSwapBuffers(engine->display, engine->surface);
}
void android_async_unlock(struct engine* engine)
{
mutex_count--;
if (mutex_count == 0)
eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
mutex.unlock();
}
struct locker
{
struct engine* e;
locker(struct engine* _e){ e = _e; android_async_lock(e); }
~locker(){ android_async_unlock(e); }
};
// see https://groups.google.com/forum/#!topic/android-ndk/Tk3g00wLKhk
void displayKeyboard(android_app* mApplication, bool pShow)
{
// Attaches the current thread to the JVM.
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = mApplication->activity->vm;
JNIEnv* lJNIEnv = mApplication->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv,
&lJavaVMAttachArgs);
if (lResult == JNI_ERR) {
return;
}
// Retrieves NativeActivity.
jobject lNativeActivity = mApplication->activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
// Retrieves Context.INPUT_METHOD_SERVICE.
jclass ClassContext = lJNIEnv->FindClass("android/content/Context");
jfieldID FieldINPUT_METHOD_SERVICE =
lJNIEnv->GetStaticFieldID(ClassContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
jobject INPUT_METHOD_SERVICE =
lJNIEnv->GetStaticObjectField(ClassContext,
FieldINPUT_METHOD_SERVICE);
//jniCheck(INPUT_METHOD_SERVICE);
// Runs getSystemService(Context.INPUT_METHOD_SERVICE).
jclass ClassInputMethodManager = lJNIEnv->FindClass(
"android/view/inputmethod/InputMethodManager");
jmethodID MethodGetSystemService = lJNIEnv->GetMethodID(
ClassNativeActivity, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
jobject lInputMethodManager = lJNIEnv->CallObjectMethod(
lNativeActivity, MethodGetSystemService,
INPUT_METHOD_SERVICE);
// Runs getWindow().getDecorView().
jmethodID MethodGetWindow = lJNIEnv->GetMethodID(
ClassNativeActivity, "getWindow",
"()Landroid/view/Window;");
jobject lWindow = lJNIEnv->CallObjectMethod(lNativeActivity,
MethodGetWindow);
jclass ClassWindow = lJNIEnv->FindClass(
"android/view/Window");
jmethodID MethodGetDecorView = lJNIEnv->GetMethodID(
ClassWindow, "getDecorView", "()Landroid/view/View;");
jobject lDecorView = lJNIEnv->CallObjectMethod(lWindow,
MethodGetDecorView);
if (pShow) {
// Runs lInputMethodManager.showSoftInput(...).
jmethodID MethodShowSoftInput = lJNIEnv->GetMethodID(
ClassInputMethodManager, "showSoftInput",
"(Landroid/view/View;I)Z");
jboolean lResult = lJNIEnv->CallBooleanMethod(
lInputMethodManager, MethodShowSoftInput,
lDecorView, lFlags);
} else {
// Runs lWindow.getViewToken()
jclass ClassView = lJNIEnv->FindClass(
"android/view/View");
jmethodID MethodGetWindowToken = lJNIEnv->GetMethodID(
ClassView, "getWindowToken", "()Landroid/os/IBinder;");
jobject lBinder = lJNIEnv->CallObjectMethod(lDecorView,
MethodGetWindowToken);
// lInputMethodManager.hideSoftInput(...).
jmethodID MethodHideSoftInput = lJNIEnv->GetMethodID(
ClassInputMethodManager, "hideSoftInputFromWindow",
"(Landroid/os/IBinder;I)Z");
jboolean lRes = lJNIEnv->CallBooleanMethod(
lInputMethodManager, MethodHideSoftInput,
lBinder, lFlags);
}
// Finished with the JVM.
lJavaVM->DetachCurrentThread();
}
/*
* Class: com_omixlab_panopainter_MainActivity
* Method: pickFileCallback
* Signature: (Ljava/lang/String;)V
*/
std::function<void(std::string)> pick_file_callback;
std::function<void()> pick_file_callback_context;
extern "C"
{
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";
}
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)
{
//if (!android_async_trylock(&g_engine))
// return;
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);
//App::I.redraw = true;
//App::I.resize(App::I.width, App::I.height);
//g_engine.width = wnd_w;
//g_engine.height = wnd_h;
//android_async_unlock(&g_engine);
}
}
void android_pick_file(android_app* mApplication, std::function<void(std::string)> callback)
{
// Attaches the current thread to the JVM.
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = mApplication->activity->vm;
JNIEnv* lJNIEnv = mApplication->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
{
return;
}
pick_file_callback = callback;
// Retrieves NativeActivity.
jobject lNativeActivity = mApplication->activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
jmethodID MethodPickFile = lJNIEnv->GetMethodID(ClassNativeActivity, "pickFile", "()V");
lJNIEnv->CallVoidMethod(lNativeActivity, MethodPickFile);
// Finished with the JVM.
lJavaVM->DetachCurrentThread();
}
float get_display_density(android_app* mApplication)
{
// Attaches the current thread to the JVM.
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = mApplication->activity->vm;
JNIEnv* lJNIEnv = mApplication->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
{
return 1;
}
// Retrieves NativeActivity.
jobject lNativeActivity = mApplication->activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
jmethodID MethodPickFile = lJNIEnv->GetMethodID(ClassNativeActivity, "getDensity", "()F");
float density = lJNIEnv->CallFloatMethod(lNativeActivity, MethodPickFile);
// Finished with the JVM.
lJavaVM->DetachCurrentThread();
return density;
}
std::string get_data_path(android_app* mApplication)
{
// Attaches the current thread to the JVM.
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = mApplication->activity->vm;
JNIEnv* lJNIEnv = mApplication->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
{
return "";
}
// Retrieves NativeActivity.
jobject lNativeActivity = mApplication->activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
jmethodID MethodPickFile = lJNIEnv->GetMethodID(ClassNativeActivity, "getDataPath", "()Ljava/lang/String;");
jstring path = (jstring)lJNIEnv->CallObjectMethod(lNativeActivity, MethodPickFile);
const char* path_utf = lJNIEnv->GetStringUTFChars(path, nullptr);
std::string file_path = path_utf; // create a copy
lJNIEnv->ReleaseStringUTFChars(path, path_utf);
// Finished with the JVM.
lJavaVM->DetachCurrentThread();
return file_path;
}
/**
* Initialize an EGL context for the current display.
*/
static int engine_init_display(struct engine* engine) {
// initialize OpenGL ES and EGL
App::I.initLog();
/*
* 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
*/
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_NONE
};
EGLint w, h, dummy, format;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
EGLDisplay display = g_display;
if (g_display == EGL_NO_DISPLAY)
{
LOG("DYSPLAY CREATE");
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
}
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<EGLConfig[]> 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, 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_DEPTH_SIZE, &d) &&
r == 8 && g == 8 && b == 8 && d == 0 ) {
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, NULL);
const EGLint attribs_test[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
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, 2,
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, 2,
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, 2,
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;
float density = get_display_density(engine->app);
LOG("density %f", density);
App::I.zoom = density / 1.5;
g_display = display;
g_context = context;
if (resuming_context)
{
LOG("RESUME APP");
App::I.and_app = engine->app;
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<std::string, bool> 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");
}
}
int ret = -1;
FILE* file = popen("getprop", "r");
std::map<std::string, std::string> 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());
//LOG("PROP: %s", os_props[""].c_str());
//LOG("PROP: %s", os_props[""].c_str());
//LOG("PROP: %s", os_props[""].c_str());
// Initialize GL state.
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
//glEnable(GL_CULL_FACE);
//glShadeModel(GL_SMOOTH);
glDisable(GL_DEPTH_TEST);
//glEnableClientState(GL_VERTEX_ARRAY);
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(engine->app);
LOG("data_path %s", App::I.data_path.c_str());
App::I.width = w;
App::I.height = h;
App::I.redraw = true;
App::I.init();
LOG("All ready");
engine->animating = 1;
return 0;
}
/**
* Just the current frame in the display.
*/
static void engine_draw_frame(struct engine* engine) {
static auto start = std::chrono::high_resolution_clock::now();
static float elapsed = 0;
locker _lock(engine);
if (engine->display == EGL_NO_DISPLAY)
return;
/*
int w, h;
eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w);
eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h);
if (w != engine->width || h != engine->height) {
engine->width = w;
engine->height = h;
App::I.resize(w, h);
App::I.redraw = true;
LOG("resize window to %d %d", w, h);
}
*/
auto now = std::chrono::high_resolution_clock::now();
auto dt = std::chrono::duration<float>(now - start);
start = now;
elapsed += dt.count();
App::I.tick(dt.count());
if (!(App::I.redraw || App::I.animate))
return;
App::I.clear();
App::I.update(elapsed);
elapsed = 0;
eglSwapBuffers(engine->display, engine->surface);
}
/**
* Tear down the EGL context currently associated with the display.
*/
static void engine_term_display(struct engine* engine) {
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;
}
/**
* 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.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");
if (tracked == 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.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.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.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.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.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(app, 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.key_char(uniValue);
break;
case AKEY_EVENT_ACTION_DOWN:
LOG("Received key down event: %d\n", key_val);
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.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);
engine_draw_frame(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;
engine_draw_frame(engine);
*/
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();
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;
// 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 ? 100 : -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;
engine_draw_frame(&g_engine);
}
// 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;
}
// Drawing is throttled to the screen update rate, so there
// is no need to do timing here.
engine_draw_frame(&g_engine);
}
}
}
//END_INCLUDE(all)