1/*
2 * Copyright (C) 2016 Google, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <cassert>
18#include <dlfcn.h>
19#include <time.h>
20#include <android/log.h>
21
22#include "Helpers.h"
23#include "Game.h"
24#include "ShellAndroid.h"
25
26namespace {
27
28// copied from ShellXCB.cpp
29class PosixTimer {
30   public:
31    PosixTimer() { reset(); }
32
33    void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); }
34
35    double get() const {
36        struct timespec now;
37        clock_gettime(CLOCK_MONOTONIC, &now);
38
39        constexpr long one_s_in_ns = 1000 * 1000 * 1000;
40        constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns);
41
42        time_t s = now.tv_sec - start_.tv_sec;
43        long ns;
44        if (now.tv_nsec > start_.tv_nsec) {
45            ns = now.tv_nsec - start_.tv_nsec;
46        } else {
47            assert(s > 0);
48            s--;
49            ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec);
50        }
51
52        return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d;
53    }
54
55   private:
56    struct timespec start_;
57};
58
59}  // namespace
60
61std::vector<std::string> ShellAndroid::get_args(android_app &app) {
62    const char intent_extra_data_key[] = "args";
63    std::vector<std::string> args;
64
65    JavaVM &vm = *app.activity->vm;
66    JNIEnv *p_env;
67    if (vm.AttachCurrentThread(&p_env, nullptr) != JNI_OK) return args;
68
69    JNIEnv &env = *p_env;
70    jobject activity = app.activity->clazz;
71    jmethodID get_intent_method = env.GetMethodID(env.GetObjectClass(activity), "getIntent", "()Landroid/content/Intent;");
72    jobject intent = env.CallObjectMethod(activity, get_intent_method);
73
74    jmethodID get_string_extra_method =
75        env.GetMethodID(env.GetObjectClass(intent), "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
76    jvalue get_string_extra_args;
77    get_string_extra_args.l = env.NewStringUTF(intent_extra_data_key);
78    jstring extra_str = static_cast<jstring>(env.CallObjectMethodA(intent, get_string_extra_method, &get_string_extra_args));
79
80    std::string args_str;
81    if (extra_str) {
82        const char *extra_utf = env.GetStringUTFChars(extra_str, nullptr);
83        args_str = extra_utf;
84        env.ReleaseStringUTFChars(extra_str, extra_utf);
85
86        env.DeleteLocalRef(extra_str);
87    }
88
89    env.DeleteLocalRef(get_string_extra_args.l);
90    env.DeleteLocalRef(intent);
91
92    vm.DetachCurrentThread();
93
94    // split args_str
95    std::stringstream ss(args_str);
96    std::string arg;
97    while (std::getline(ss, arg, ' ')) {
98        if (!arg.empty()) args.push_back(arg);
99    }
100
101    return args;
102}
103
104ShellAndroid::ShellAndroid(android_app &app, Game &game) : Shell(game), app_(app) {
105    if (game.settings().validate) {
106        instance_layers_.push_back("VK_LAYER_GOOGLE_threading");
107        instance_layers_.push_back("VK_LAYER_LUNARG_parameter_validation");
108        instance_layers_.push_back("VK_LAYER_LUNARG_object_tracker");
109        instance_layers_.push_back("VK_LAYER_LUNARG_core_validation");
110        instance_layers_.push_back("VK_LAYER_GOOGLE_unique_objects");
111    }
112
113    instance_extensions_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
114
115    app_.userData = this;
116    app_.onAppCmd = on_app_cmd;
117    app_.onInputEvent = on_input_event;
118
119    init_vk();
120}
121
122ShellAndroid::~ShellAndroid() {
123    cleanup_vk();
124    dlclose(lib_handle_);
125}
126
127void ShellAndroid::log(LogPriority priority, const char *msg) {
128    int prio;
129
130    switch (priority) {
131        case LOG_DEBUG:
132            prio = ANDROID_LOG_DEBUG;
133            break;
134        case LOG_INFO:
135            prio = ANDROID_LOG_INFO;
136            break;
137        case LOG_WARN:
138            prio = ANDROID_LOG_WARN;
139            break;
140        case LOG_ERR:
141            prio = ANDROID_LOG_ERROR;
142            break;
143        default:
144            prio = ANDROID_LOG_UNKNOWN;
145            break;
146    }
147
148    __android_log_write(prio, settings_.name.c_str(), msg);
149}
150
151PFN_vkGetInstanceProcAddr ShellAndroid::load_vk() {
152    const char filename[] = "libvulkan.so";
153    void *handle = nullptr, *symbol = nullptr;
154
155    handle = dlopen(filename, RTLD_LAZY);
156    if (handle) symbol = dlsym(handle, "vkGetInstanceProcAddr");
157    if (!symbol) {
158        if (handle) dlclose(handle);
159
160        throw std::runtime_error(dlerror());
161    }
162
163    lib_handle_ = handle;
164
165    return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol);
166}
167
168VkSurfaceKHR ShellAndroid::create_surface(VkInstance instance) {
169    VkAndroidSurfaceCreateInfoKHR surface_info = {};
170    surface_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
171    surface_info.window = app_.window;
172
173    VkSurfaceKHR surface;
174    vk::assert_success(vk::CreateAndroidSurfaceKHR(instance, &surface_info, nullptr, &surface));
175
176    return surface;
177}
178
179void ShellAndroid::on_app_cmd(int32_t cmd) {
180    switch (cmd) {
181        case APP_CMD_INIT_WINDOW:
182            create_context();
183            resize_swapchain(0, 0);
184            break;
185        case APP_CMD_TERM_WINDOW:
186            destroy_context();
187            break;
188        case APP_CMD_WINDOW_RESIZED:
189            resize_swapchain(0, 0);
190            break;
191        case APP_CMD_STOP:
192            ANativeActivity_finish(app_.activity);
193            break;
194        default:
195            break;
196    }
197}
198
199int32_t ShellAndroid::on_input_event(const AInputEvent *event) {
200    if (AInputEvent_getType(event) != AINPUT_EVENT_TYPE_MOTION) return false;
201
202    bool handled = false;
203
204    switch (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK) {
205        case AMOTION_EVENT_ACTION_UP:
206            game_.on_key(Game::KEY_SPACE);
207            handled = true;
208            break;
209        default:
210            break;
211    }
212
213    return handled;
214}
215
216void ShellAndroid::quit() { ANativeActivity_finish(app_.activity); }
217
218void ShellAndroid::run() {
219    PosixTimer timer;
220
221    double current_time = timer.get();
222
223    while (true) {
224        struct android_poll_source *source;
225        while (true) {
226            int timeout = (settings_.animate && app_.window) ? 0 : -1;
227            if (ALooper_pollAll(timeout, nullptr, nullptr, reinterpret_cast<void **>(&source)) < 0) break;
228
229            if (source) source->process(&app_, source);
230        }
231
232        if (app_.destroyRequested) break;
233
234        if (!app_.window) continue;
235
236        acquire_back_buffer();
237
238        double t = timer.get();
239        add_game_time(static_cast<float>(t - current_time));
240
241        present_back_buffer();
242
243        current_time = t;
244    }
245}
246