1 /*
2 * Copyright (C) 2016 Google, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 */
22
23 #include <cassert>
24 #include <dlfcn.h>
25 #include <time.h>
26 #include <android/log.h>
27
28 #include "Helpers.h"
29 #include "Game.h"
30 #include "ShellAndroid.h"
31
32 namespace {
33
34 // copied from ShellXCB.cpp
35 class PosixTimer {
36 public:
PosixTimer()37 PosixTimer()
38 {
39 reset();
40 }
41
reset()42 void reset()
43 {
44 clock_gettime(CLOCK_MONOTONIC, &start_);
45 }
46
get() const47 double get() const
48 {
49 struct timespec now;
50 clock_gettime(CLOCK_MONOTONIC, &now);
51
52 constexpr long one_s_in_ns = 1000 * 1000 * 1000;
53 constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns);
54
55 time_t s = now.tv_sec - start_.tv_sec;
56 long ns;
57 if (now.tv_nsec > start_.tv_nsec) {
58 ns = now.tv_nsec - start_.tv_nsec;
59 } else {
60 assert(s > 0);
61 s--;
62 ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec);
63 }
64
65 return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d;
66 }
67
68 private:
69 struct timespec start_;
70 };
71
72 } // namespace
73
ShellAndroid(android_app & app,Game & game)74 ShellAndroid::ShellAndroid(android_app &app, Game &game) : Shell(game), app_(app)
75 {
76 instance_extensions_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
77
78 app_dummy();
79 app_.userData = this;
80 app_.onAppCmd = on_app_cmd;
81 app_.onInputEvent = on_input_event;
82
83 init_vk();
84 }
85
~ShellAndroid()86 ShellAndroid::~ShellAndroid()
87 {
88 cleanup_vk();
89 dlclose(lib_handle_);
90 }
91
log(LogPriority priority,const char * msg)92 void ShellAndroid::log(LogPriority priority, const char *msg)
93 {
94 int prio;
95
96 switch (priority) {
97 case LOG_DEBUG:
98 prio = ANDROID_LOG_DEBUG;
99 break;
100 case LOG_INFO:
101 prio = ANDROID_LOG_INFO;
102 break;
103 case LOG_WARN:
104 prio = ANDROID_LOG_WARN;
105 break;
106 case LOG_ERR:
107 prio = ANDROID_LOG_ERROR;
108 break;
109 default:
110 prio = ANDROID_LOG_UNKNOWN;
111 break;
112 }
113
114 __android_log_write(prio, settings_.name.c_str(), msg);
115 }
116
load_vk()117 PFN_vkGetInstanceProcAddr ShellAndroid::load_vk()
118 {
119 const char filename[] = "libvulkan.so";
120 void *handle = nullptr, *symbol = nullptr;
121
122 handle = dlopen(filename, RTLD_LAZY);
123 if (handle)
124 symbol = dlsym(handle, "vkGetInstanceProcAddr");
125 if (!symbol) {
126 if (handle)
127 dlclose(handle);
128
129 throw std::runtime_error(dlerror());
130 }
131
132 lib_handle_ = handle;
133
134 return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol);
135 }
136
create_surface(VkInstance instance)137 VkSurfaceKHR ShellAndroid::create_surface(VkInstance instance)
138 {
139 VkAndroidSurfaceCreateInfoKHR surface_info = {};
140 surface_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
141 surface_info.window = app_.window;
142
143 VkSurfaceKHR surface;
144 vk::assert_success(vk::CreateAndroidSurfaceKHR(instance, &surface_info, nullptr, &surface));
145
146 return surface;
147 }
148
on_app_cmd(int32_t cmd)149 void ShellAndroid::on_app_cmd(int32_t cmd)
150 {
151 switch (cmd) {
152 case APP_CMD_INIT_WINDOW:
153 create_context();
154 resize_swapchain(0, 0);
155 break;
156 case APP_CMD_TERM_WINDOW:
157 destroy_context();
158 break;
159 case APP_CMD_WINDOW_RESIZED:
160 resize_swapchain(0, 0);
161 break;
162 case APP_CMD_STOP:
163 ANativeActivity_finish(app_.activity);
164 break;
165 default:
166 break;
167 }
168 }
169
on_input_event(const AInputEvent * event)170 int32_t ShellAndroid::on_input_event(const AInputEvent *event)
171 {
172 if (AInputEvent_getType(event) != AINPUT_EVENT_TYPE_MOTION)
173 return false;
174
175 bool handled = false;
176
177 switch (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK) {
178 case AMOTION_EVENT_ACTION_UP:
179 game_.on_key(Game::KEY_SPACE);
180 handled = true;
181 break;
182 default:
183 break;
184 }
185
186 return handled;
187 }
188
quit()189 void ShellAndroid::quit()
190 {
191 ANativeActivity_finish(app_.activity);
192 }
193
run()194 void ShellAndroid::run()
195 {
196 PosixTimer timer;
197
198 double current_time = timer.get();
199
200 while (true) {
201 struct android_poll_source *source;
202 while (true) {
203 int timeout = (settings_.animate && app_.window) ? 0 : -1;
204 if (ALooper_pollAll(timeout, nullptr, nullptr,
205 reinterpret_cast<void **>(&source)) < 0)
206 break;
207
208 if (source)
209 source->process(&app_, source);
210 }
211
212 if (app_.destroyRequested)
213 break;
214
215 if (!app_.window)
216 continue;
217
218 acquire_back_buffer();
219
220 double t = timer.get();
221 add_game_time(static_cast<float>(t - current_time));
222
223 present_back_buffer();
224
225 current_time = t;
226 }
227 }
228