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 {
30public:
31    PosixTimer()
32    {
33        reset();
34    }
35
36    void reset()
37    {
38        clock_gettime(CLOCK_MONOTONIC, &start_);
39    }
40
41    double get() const
42    {
43        struct timespec now;
44        clock_gettime(CLOCK_MONOTONIC, &now);
45
46        constexpr long one_s_in_ns = 1000 * 1000 * 1000;
47        constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns);
48
49        time_t s = now.tv_sec - start_.tv_sec;
50        long ns;
51        if (now.tv_nsec > start_.tv_nsec) {
52            ns = now.tv_nsec - start_.tv_nsec;
53        } else {
54            assert(s > 0);
55            s--;
56            ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec);
57        }
58
59        return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d;
60    }
61
62private:
63    struct timespec start_;
64};
65
66} // namespace
67
68ShellAndroid::ShellAndroid(android_app &app, Game &game) : Shell(game), app_(app)
69{
70    instance_extensions_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
71
72    app_dummy();
73    app_.userData = this;
74    app_.onAppCmd = on_app_cmd;
75    app_.onInputEvent = on_input_event;
76
77    init_vk();
78}
79
80ShellAndroid::~ShellAndroid()
81{
82    cleanup_vk();
83    dlclose(lib_handle_);
84}
85
86void ShellAndroid::log(LogPriority priority, const char *msg)
87{
88    int prio;
89
90    switch (priority) {
91    case LOG_DEBUG:
92        prio = ANDROID_LOG_DEBUG;
93        break;
94    case LOG_INFO:
95        prio = ANDROID_LOG_INFO;
96        break;
97    case LOG_WARN:
98        prio = ANDROID_LOG_WARN;
99        break;
100    case LOG_ERR:
101        prio = ANDROID_LOG_ERROR;
102        break;
103    default:
104        prio = ANDROID_LOG_UNKNOWN;
105        break;
106    }
107
108    __android_log_write(prio, settings_.name.c_str(), msg);
109}
110
111PFN_vkGetInstanceProcAddr ShellAndroid::load_vk()
112{
113    const char filename[] = "libvulkan.so";
114    void *handle = nullptr, *symbol = nullptr;
115
116    handle = dlopen(filename, RTLD_LAZY);
117    if (handle)
118        symbol = dlsym(handle, "vkGetInstanceProcAddr");
119    if (!symbol) {
120        if (handle)
121            dlclose(handle);
122
123        throw std::runtime_error(dlerror());
124    }
125
126    lib_handle_ = handle;
127
128    return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol);
129}
130
131VkSurfaceKHR ShellAndroid::create_surface(VkInstance instance)
132{
133    VkAndroidSurfaceCreateInfoKHR surface_info = {};
134    surface_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
135    surface_info.window = app_.window;
136
137    VkSurfaceKHR surface;
138    vk::assert_success(vk::CreateAndroidSurfaceKHR(instance, &surface_info, nullptr, &surface));
139
140    return surface;
141}
142
143void ShellAndroid::on_app_cmd(int32_t cmd)
144{
145    switch (cmd) {
146    case APP_CMD_INIT_WINDOW:
147        create_context();
148        resize_swapchain(0, 0);
149        break;
150    case APP_CMD_TERM_WINDOW:
151        destroy_context();
152        break;
153    case APP_CMD_WINDOW_RESIZED:
154        resize_swapchain(0, 0);
155        break;
156    case APP_CMD_STOP:
157        ANativeActivity_finish(app_.activity);
158        break;
159    default:
160        break;
161    }
162}
163
164int32_t ShellAndroid::on_input_event(const AInputEvent *event)
165{
166    if (AInputEvent_getType(event) != AINPUT_EVENT_TYPE_MOTION)
167        return false;
168
169    bool handled = false;
170
171    switch (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK) {
172    case AMOTION_EVENT_ACTION_UP:
173        game_.on_key(Game::KEY_SPACE);
174        handled = true;
175        break;
176    default:
177        break;
178    }
179
180    return handled;
181}
182
183void ShellAndroid::quit()
184{
185    ANativeActivity_finish(app_.activity);
186}
187
188void ShellAndroid::run()
189{
190    PosixTimer timer;
191
192    double current_time = timer.get();
193
194    while (true) {
195        struct android_poll_source *source;
196        while (true) {
197            int timeout = (settings_.animate && app_.window) ? 0 : -1;
198            if (ALooper_pollAll(timeout, nullptr, nullptr,
199                    reinterpret_cast<void **>(&source)) < 0)
200                break;
201
202            if (source)
203                source->process(&app_, source);
204        }
205
206        if (app_.destroyRequested)
207            break;
208
209        if (!app_.window)
210            continue;
211
212        acquire_back_buffer();
213
214        double t = timer.get();
215        add_game_time(static_cast<float>(t - current_time));
216
217        present_back_buffer();
218
219        current_time = t;
220    }
221}
222