/* * Copyright (C) 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "Helpers.h" #include "Game.h" #include "ShellAndroid.h" namespace { // copied from ShellXCB.cpp class PosixTimer { public: PosixTimer() { reset(); } void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); } double get() const { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); constexpr long one_s_in_ns = 1000 * 1000 * 1000; constexpr double one_s_in_ns_d = static_cast(one_s_in_ns); time_t s = now.tv_sec - start_.tv_sec; long ns; if (now.tv_nsec > start_.tv_nsec) { ns = now.tv_nsec - start_.tv_nsec; } else { assert(s > 0); s--; ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec); } return static_cast(s) + static_cast(ns) / one_s_in_ns_d; } private: struct timespec start_; }; } // namespace std::vector ShellAndroid::get_args(android_app &app) { const char intent_extra_data_key[] = "args"; std::vector args; JavaVM &vm = *app.activity->vm; JNIEnv *p_env; if (vm.AttachCurrentThread(&p_env, nullptr) != JNI_OK) return args; JNIEnv &env = *p_env; jobject activity = app.activity->clazz; jmethodID get_intent_method = env.GetMethodID(env.GetObjectClass(activity), "getIntent", "()Landroid/content/Intent;"); jobject intent = env.CallObjectMethod(activity, get_intent_method); jmethodID get_string_extra_method = env.GetMethodID(env.GetObjectClass(intent), "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;"); jvalue get_string_extra_args; get_string_extra_args.l = env.NewStringUTF(intent_extra_data_key); jstring extra_str = static_cast(env.CallObjectMethodA(intent, get_string_extra_method, &get_string_extra_args)); std::string args_str; if (extra_str) { const char *extra_utf = env.GetStringUTFChars(extra_str, nullptr); args_str = extra_utf; env.ReleaseStringUTFChars(extra_str, extra_utf); env.DeleteLocalRef(extra_str); } env.DeleteLocalRef(get_string_extra_args.l); env.DeleteLocalRef(intent); vm.DetachCurrentThread(); // split args_str std::stringstream ss(args_str); std::string arg; while (std::getline(ss, arg, ' ')) { if (!arg.empty()) args.push_back(arg); } return args; } ShellAndroid::ShellAndroid(android_app &app, Game &game) : Shell(game), app_(app) { if (game.settings().validate) { instance_layers_.push_back("VK_LAYER_GOOGLE_threading"); instance_layers_.push_back("VK_LAYER_LUNARG_parameter_validation"); instance_layers_.push_back("VK_LAYER_LUNARG_object_tracker"); instance_layers_.push_back("VK_LAYER_LUNARG_image"); instance_layers_.push_back("VK_LAYER_LUNARG_core_validation"); instance_layers_.push_back("VK_LAYER_LUNARG_swapchain"); instance_layers_.push_back("VK_LAYER_GOOGLE_unique_objects"); } instance_extensions_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); app_dummy(); app_.userData = this; app_.onAppCmd = on_app_cmd; app_.onInputEvent = on_input_event; init_vk(); } ShellAndroid::~ShellAndroid() { cleanup_vk(); dlclose(lib_handle_); } void ShellAndroid::log(LogPriority priority, const char *msg) { int prio; switch (priority) { case LOG_DEBUG: prio = ANDROID_LOG_DEBUG; break; case LOG_INFO: prio = ANDROID_LOG_INFO; break; case LOG_WARN: prio = ANDROID_LOG_WARN; break; case LOG_ERR: prio = ANDROID_LOG_ERROR; break; default: prio = ANDROID_LOG_UNKNOWN; break; } __android_log_write(prio, settings_.name.c_str(), msg); } PFN_vkGetInstanceProcAddr ShellAndroid::load_vk() { const char filename[] = "libvulkan.so"; void *handle = nullptr, *symbol = nullptr; handle = dlopen(filename, RTLD_LAZY); if (handle) symbol = dlsym(handle, "vkGetInstanceProcAddr"); if (!symbol) { if (handle) dlclose(handle); throw std::runtime_error(dlerror()); } lib_handle_ = handle; return reinterpret_cast(symbol); } VkSurfaceKHR ShellAndroid::create_surface(VkInstance instance) { VkAndroidSurfaceCreateInfoKHR surface_info = {}; surface_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; surface_info.window = app_.window; VkSurfaceKHR surface; vk::assert_success(vk::CreateAndroidSurfaceKHR(instance, &surface_info, nullptr, &surface)); return surface; } void ShellAndroid::on_app_cmd(int32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: create_context(); resize_swapchain(0, 0); break; case APP_CMD_TERM_WINDOW: destroy_context(); break; case APP_CMD_WINDOW_RESIZED: resize_swapchain(0, 0); break; case APP_CMD_STOP: ANativeActivity_finish(app_.activity); break; default: break; } } int32_t ShellAndroid::on_input_event(const AInputEvent *event) { if (AInputEvent_getType(event) != AINPUT_EVENT_TYPE_MOTION) return false; bool handled = false; switch (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_UP: game_.on_key(Game::KEY_SPACE); handled = true; break; default: break; } return handled; } void ShellAndroid::quit() { ANativeActivity_finish(app_.activity); } void ShellAndroid::run() { PosixTimer timer; double current_time = timer.get(); while (true) { struct android_poll_source *source; while (true) { int timeout = (settings_.animate && app_.window) ? 0 : -1; if (ALooper_pollAll(timeout, nullptr, nullptr, reinterpret_cast(&source)) < 0) break; if (source) source->process(&app_, source); } if (app_.destroyRequested) break; if (!app_.window) continue; acquire_back_buffer(); double t = timer.get(); add_game_time(static_cast(t - current_time)); present_back_buffer(); current_time = t; } }