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