You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

406 lines
11 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright © 2010—2019 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // Lol Engine is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #include <lol/engine-internal.h>
  13. #if __ANDROID__
  14. #include <jni.h>
  15. #include <sys/types.h>
  16. #include <android/asset_manager_jni.h>
  17. #include <EGL/egl.h>
  18. #include <GLES/gl.h>
  19. extern "C" {
  20. #include <android_native_app_glue.h>
  21. #include <android_native_app_glue.c>
  22. }
  23. #include "androidapp.h"
  24. #include "input/input_internal.h"
  25. using namespace lol;
  26. namespace lol
  27. {
  28. ANativeActivity *g_activity;
  29. AAssetManager *g_assets;
  30. }; /* namespace lol */
  31. extern "C" jint
  32. JNI_OnLoad(JavaVM* vm, void* reserved)
  33. {
  34. msg::debug("Java layer loading library, vm=0x%08lx", (long)(intptr_t)vm);
  35. return JNI_VERSION_1_4;
  36. }
  37. /* One of these wrappers will be overridden by the user's version */
  38. void lol_android_main(void) __attribute__((weak));
  39. void lol_android_main(int argc, char **argv) __attribute__((weak));
  40. void lol_android_main(int argc, char **argv, char **envp) __attribute__((weak));
  41. /**
  42. * Our saved state data.
  43. */
  44. struct SavedState
  45. {
  46. ivec2 position;
  47. };
  48. /**
  49. * Shared state for our app.
  50. */
  51. class lol::AndroidAppData
  52. {
  53. public:
  54. int CreateDisplay();
  55. void DestroyDisplay();
  56. void DrawFrame();
  57. static void StaticHandleCommand(android_app* native_app, int32_t cmd)
  58. {
  59. return ((AndroidAppData*)native_app->userData)->HandleCommand(cmd);
  60. }
  61. static int32_t StaticHandleInput(android_app* native_app, AInputEvent* ev)
  62. {
  63. return ((AndroidAppData*)native_app->userData)->HandleInput(ev);
  64. }
  65. android_app* m_native_app;
  66. /* The resolution that was asked (not the one we got) */
  67. /* FIXME: we need proper unproject or at least screen space events!! */
  68. ivec2 m_wanted_resolution;
  69. InputDeviceInternal* m_mouse;
  70. SavedState m_state;
  71. bool m_video_ready;
  72. private:
  73. void HandleCommand(int32_t cmd);
  74. int32_t HandleInput(AInputEvent* event);
  75. vec2 m_prev_pos;
  76. EGLDisplay m_display;
  77. EGLSurface m_surface;
  78. EGLContext m_context;
  79. };
  80. /**
  81. * Initialize an EGL context for the current display.
  82. */
  83. int lol::AndroidAppData::CreateDisplay()
  84. {
  85. /* FIXME: there is a lot of code common to eglapp.cpp here. */
  86. const EGLint attribs[] =
  87. {
  88. EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
  89. EGL_BUFFER_SIZE, 16,
  90. EGL_DEPTH_SIZE, 16,
  91. EGL_RED_SIZE, 4,
  92. EGL_GREEN_SIZE, 4,
  93. EGL_BLUE_SIZE, 4,
  94. EGL_ALPHA_SIZE, 4,
  95. EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
  96. EGL_NONE
  97. };
  98. EGLint w, h, dummy, format;
  99. EGLint numConfigs;
  100. EGLConfig config;
  101. m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  102. eglInitialize(m_display, 0, 0);
  103. eglChooseConfig(m_display, attribs, &config, 1, &numConfigs);
  104. eglGetConfigAttrib(m_display, config, EGL_NATIVE_VISUAL_ID, &format);
  105. ANativeWindow_setBuffersGeometry(m_native_app->window,
  106. 0, 0, format);
  107. m_surface = eglCreateWindowSurface(m_display, config,
  108. m_native_app->window, nullptr);
  109. EGLint ctxattr[] =
  110. {
  111. EGL_CONTEXT_CLIENT_VERSION, 2,
  112. EGL_NONE
  113. };
  114. m_context = eglCreateContext(m_display, config, EGL_NO_CONTEXT, ctxattr);
  115. if (eglMakeCurrent(m_display, m_surface, m_surface, m_context) == EGL_FALSE)
  116. {
  117. msg::error("unable to eglMakeCurrent");
  118. return -1;
  119. }
  120. eglQuerySurface(m_display, m_surface, EGL_WIDTH, &w);
  121. eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &h);
  122. /* Launch our renderer */
  123. msg::debug("Java layer initialising renderer (%dx%d)", w, h);
  124. Video::Setup(ivec2(w, h));
  125. return 0;
  126. }
  127. void lol::AndroidAppData::DrawFrame()
  128. {
  129. if (!m_display)
  130. return;
  131. Ticker::tick_draw();
  132. eglSwapBuffers(m_display, m_surface);
  133. }
  134. /**
  135. * Tear down the EGL context currently associated with the display.
  136. */
  137. void lol::AndroidAppData::DestroyDisplay()
  138. {
  139. if (m_display != EGL_NO_DISPLAY)
  140. {
  141. eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  142. if (m_context != EGL_NO_CONTEXT)
  143. {
  144. eglDestroyContext(m_display, m_context);
  145. }
  146. if (m_surface != EGL_NO_SURFACE)
  147. {
  148. eglDestroySurface(m_display, m_surface);
  149. }
  150. eglTerminate(m_display);
  151. }
  152. m_display = EGL_NO_DISPLAY;
  153. m_context = EGL_NO_CONTEXT;
  154. m_surface = EGL_NO_SURFACE;
  155. }
  156. /**
  157. * Process the next input event.
  158. */
  159. int32_t lol::AndroidAppData::HandleInput(AInputEvent* event)
  160. {
  161. switch (AInputEvent_getType(event))
  162. {
  163. case AINPUT_EVENT_TYPE_MOTION:
  164. // We need the max if we want consistent mouse speed between axis
  165. float max_screen_size = lol::max(m_wanted_resolution.x,
  166. m_wanted_resolution.y);
  167. /* FIXME: we flip the Y axis here, but is it the right place? */
  168. ivec2 pos(AMotionEvent_getX(event, 0),
  169. AMotionEvent_getY(event, 0));
  170. pos *= m_wanted_resolution / Video::GetSize();
  171. pos.y = m_wanted_resolution.y - 1 - pos.y;
  172. m_mouse->SetCursor(0, vec2(pos) / vec2(m_wanted_resolution), pos);
  173. // Note: 100.0f is an arbitrary value that makes it feel about the same than an xbox controller joystick
  174. m_mouse->internal_set_axis(0, (pos.x - m_prev_pos.x) / max_screen_size * 100.f);
  175. // Unlike SDL, no need to negate Y axis
  176. m_mouse->internal_set_axis(1, (pos.y - m_prev_pos.y) / max_screen_size * -100.f);
  177. m_prev_pos = pos;
  178. switch (AKeyEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK)
  179. {
  180. case AMOTION_EVENT_ACTION_DOWN:
  181. m_mouse->internal_set_key(0, true);
  182. break;
  183. case AMOTION_EVENT_ACTION_UP:
  184. m_mouse->internal_set_key(0, false);
  185. break;
  186. }
  187. return 1;
  188. }
  189. return 0;
  190. }
  191. /**
  192. * Process the next main command.
  193. */
  194. void lol::AndroidAppData::HandleCommand(int32_t cmd)
  195. {
  196. switch (cmd)
  197. {
  198. case APP_CMD_SAVE_STATE:
  199. /* The system has asked us to save our current state. Do so. */
  200. m_native_app->savedState = malloc(sizeof(SavedState));
  201. *((SavedState*)m_native_app->savedState) = m_state;
  202. m_native_app->savedStateSize = sizeof(SavedState);
  203. break;
  204. case APP_CMD_INIT_WINDOW:
  205. /* The window is being shown, get it ready. */
  206. if (m_native_app->window != nullptr)
  207. {
  208. CreateDisplay();
  209. m_video_ready = true;
  210. //DrawFrame();
  211. }
  212. break;
  213. case APP_CMD_TERM_WINDOW:
  214. /* The window is being hidden or closed, clean it up. */
  215. DestroyDisplay();
  216. break;
  217. case APP_CMD_GAINED_FOCUS:
  218. break;
  219. case APP_CMD_LOST_FOCUS:
  220. /* FIXME: stop animating */
  221. DrawFrame();
  222. break;
  223. }
  224. }
  225. /* FIXME: find a better way to pass this to the AndroidApp ctor. */
  226. AndroidAppData *g_data;
  227. void android_main(android_app* native_app)
  228. {
  229. msg::debug("Java layer calling android_main() for app 0x%08lx",
  230. (long)native_app);
  231. /* Register native activity */
  232. g_activity = native_app->activity;
  233. /* Get JNI environment */
  234. JNIEnv *jni_env;
  235. jint res = g_activity->vm->GetEnv((void **)&jni_env, JNI_VERSION_1_2);
  236. if (res < 0)
  237. {
  238. msg::debug("JVM environment not found, trying to attach thread\n");
  239. res = g_activity->vm->AttachCurrentThread(&jni_env, nullptr);
  240. }
  241. if (res < 0)
  242. {
  243. msg::error("JVM environment not found, cannot run main()\n");
  244. return;
  245. }
  246. /* Get asset manager */
  247. jclass cls = jni_env->GetObjectClass(g_activity->clazz);
  248. jmethodID mid = jni_env->GetMethodID(cls, "getAssets",
  249. "()Landroid/content/res/AssetManager;");
  250. jobject assets = jni_env->CallObjectMethod(g_activity->clazz, mid);
  251. jni_env->NewGlobalRef(assets); /* FIXME: never released! */
  252. g_assets = AAssetManager_fromJava(jni_env, assets);
  253. /* Create our app data */
  254. g_data = new AndroidAppData();
  255. g_data->m_native_app = native_app;
  256. g_data->m_video_ready = false;
  257. /* Make sure glue isn't stripped */
  258. app_dummy();
  259. native_app->userData = g_data;
  260. native_app->onAppCmd = lol::AndroidAppData::StaticHandleCommand;
  261. native_app->onInputEvent = lol::AndroidAppData::StaticHandleInput;
  262. if (native_app->savedState != nullptr)
  263. {
  264. /* We are starting with a previous saved state; restore from it */
  265. g_data->m_state = *(SavedState*)native_app->savedState;
  266. }
  267. int argc = 1;
  268. char *argv[] = { const_cast<char *>(""), nullptr };
  269. char *env[] = { nullptr };
  270. /* Wait for GL context */
  271. while (!g_data->m_video_ready)
  272. {
  273. int ident, fdesc, events;
  274. struct android_poll_source* source;
  275. ident = ALooper_pollAll(0, &fdesc, &events, (void**)&source);
  276. if (ident >= 0 && source)
  277. source->process(native_app, source);
  278. }
  279. msg::debug("Java layer running real main()\n");
  280. /* Call the user's main() function. One of these will work. */
  281. lol_android_main();
  282. lol_android_main(argc, argv);
  283. lol_android_main(argc, argv, env);
  284. }
  285. lol::AndroidApp::AndroidApp(char const *title, ivec2 res, float fps)
  286. : m_data(g_data)
  287. {
  288. /* Launch our ticker */
  289. msg::debug("Java layer initialising ticker at %g fps", fps);
  290. Ticker::Setup(fps);
  291. m_data->m_wanted_resolution = res;
  292. m_data->m_mouse = InputDeviceInternal::CreateStandardMouse();
  293. }
  294. void lol::AndroidApp::ShowPointer(bool show)
  295. {
  296. }
  297. lol::AndroidApp::~AndroidApp()
  298. {
  299. m_data->DestroyDisplay();
  300. /* FIXME: handle m_data->m_mouse */
  301. delete m_data;
  302. }
  303. void lol::AndroidApp::Tick()
  304. {
  305. /* Read all pending events. */
  306. int ident;
  307. int events;
  308. struct android_poll_source* source;
  309. /* Loop until all events are read, then continue to draw the next
  310. * frame of animation. */
  311. while ((ident = ALooper_pollAll(0, nullptr, &events,
  312. (void**)&source)) >= 0)
  313. {
  314. /* Process this event */
  315. if (source)
  316. source->process(m_data->m_native_app, source);
  317. /* Check if we are exiting */
  318. if (m_data->m_native_app->destroyRequested != 0)
  319. Ticker::Shutdown();
  320. }
  321. m_data->DrawFrame();
  322. }
  323. /*
  324. * Fake main() wrappers that let us call the user’s main() from within
  325. * a separate thread.
  326. */
  327. void lol_android_main(void)
  328. {
  329. }
  330. void lol_android_main(int argc, char **argv)
  331. {
  332. UNUSED(argc, argv);
  333. }
  334. void lol_android_main(int argc, char **argv, char **envp)
  335. {
  336. UNUSED(argc, argv, envp);
  337. }
  338. #endif /* __ANDROID__ */