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
26 namespace {
27
28 // copied from ShellXCB.cpp
29 class PosixTimer {
30 public:
PosixTimer()31 PosixTimer()
32 {
33 reset();
34 }
35
reset()36 void reset()
37 {
38 clock_gettime(CLOCK_MONOTONIC, &start_);
39 }
40
get() const41 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
62 private:
63 struct timespec start_;
64 };
65
66 } // namespace
67
ShellAndroid(android_app & app,Game & game)68 ShellAndroid::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
~ShellAndroid()80 ShellAndroid::~ShellAndroid()
81 {
82 cleanup_vk();
83 dlclose(lib_handle_);
84 }
85
log(LogPriority priority,const char * msg)86 void 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
load_vk()111 PFN_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
create_surface(VkInstance instance)131 VkSurfaceKHR 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
on_app_cmd(int32_t cmd)143 void 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
on_input_event(const AInputEvent * event)164 int32_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
quit()183 void ShellAndroid::quit()
184 {
185 ANativeActivity_finish(app_.activity);
186 }
187
run()188 void 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