source: trunk/src/platform/android/androidapp.cpp @ 2618

Last change on this file since 2618 was 2618, checked in by sam, 10 years ago

android: add a temporary hack for mouse emulation on Android devices.

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