One stop solution for all Vulkan samples
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

517 lines
15 KiB

/* Copyright (c) 2019-2023, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 "android_platform.h"
#include <chrono>
#include <unistd.h>
#include <unordered_map>
#include <android/context.hpp>
#include "common/error.h"
VKBP_DISABLE_WARNINGS()
#include <fmt/format.h>
#include <imgui.h>
#include <jni.h>
#include <spdlog/sinks/android_sink.h>
#include <spdlog/sinks/basic_file_sink.h>
VKBP_ENABLE_WARNINGS()
#include "apps.h"
#include "common/logging.h"
#include "common/strings.h"
#include "platform/android/android_window.h"
#include "platform/input_events.h"
extern "C"
{
JNIEXPORT jobjectArray JNICALL
Java_com_khronos_vulkan_1samples_SampleLauncherActivity_getSamples(JNIEnv *env, jobject thiz)
{
auto sample_list = apps::get_samples();
jclass c = env->FindClass("com/khronos/vulkan_samples/model/Sample");
jmethodID constructor = env->GetMethodID(c, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
jobjectArray j_sample_list = env->NewObjectArray(sample_list.size(), c, 0);
for (int sample_index = 0; sample_index < sample_list.size(); sample_index++)
{
const apps::SampleInfo *sample_info = reinterpret_cast<apps::SampleInfo *>(sample_list[sample_index]);
jstring id = env->NewStringUTF(sample_info->id.c_str());
jstring category = env->NewStringUTF(sample_info->category.c_str());
jstring author = env->NewStringUTF(sample_info->author.c_str());
jstring name = env->NewStringUTF(sample_info->name.c_str());
jstring desc = env->NewStringUTF(sample_info->description.c_str());
jobjectArray j_tag_list = env->NewObjectArray(sample_info->tags.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
for (int tag_index = 0; tag_index < sample_info->tags.size(); ++tag_index)
{
env->SetObjectArrayElement(j_tag_list, tag_index, env->NewStringUTF(sample_info->tags[tag_index].c_str()));
}
env->SetObjectArrayElement(j_sample_list, sample_index, env->NewObject(c, constructor, id, category, author, name, desc, j_tag_list));
}
return j_sample_list;
}
}
namespace vkb
{
namespace
{
inline std::tm thread_safe_time(const std::time_t time)
{
std::tm result;
std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
result = *std::localtime(&time);
return result;
}
inline KeyCode translate_key_code(int key)
{
static const std::unordered_map<int, KeyCode> key_lookup =
{
{AKEYCODE_SPACE, KeyCode::Space},
{AKEYCODE_APOSTROPHE, KeyCode::Apostrophe},
{AKEYCODE_COMMA, KeyCode::Comma},
{AKEYCODE_MINUS, KeyCode::Minus},
{AKEYCODE_PERIOD, KeyCode::Period},
{AKEYCODE_SLASH, KeyCode::Slash},
{AKEYCODE_0, KeyCode::_0},
{AKEYCODE_1, KeyCode::_1},
{AKEYCODE_2, KeyCode::_2},
{AKEYCODE_3, KeyCode::_3},
{AKEYCODE_4, KeyCode::_4},
{AKEYCODE_5, KeyCode::_5},
{AKEYCODE_6, KeyCode::_6},
{AKEYCODE_7, KeyCode::_7},
{AKEYCODE_8, KeyCode::_8},
{AKEYCODE_9, KeyCode::_9},
{AKEYCODE_SEMICOLON, KeyCode::Semicolon},
{AKEYCODE_EQUALS, KeyCode::Equal},
{AKEYCODE_A, KeyCode::A},
{AKEYCODE_B, KeyCode::B},
{AKEYCODE_C, KeyCode::C},
{AKEYCODE_D, KeyCode::D},
{AKEYCODE_E, KeyCode::E},
{AKEYCODE_F, KeyCode::F},
{AKEYCODE_G, KeyCode::G},
{AKEYCODE_H, KeyCode::H},
{AKEYCODE_I, KeyCode::I},
{AKEYCODE_J, KeyCode::J},
{AKEYCODE_K, KeyCode::K},
{AKEYCODE_L, KeyCode::L},
{AKEYCODE_M, KeyCode::M},
{AKEYCODE_N, KeyCode::N},
{AKEYCODE_O, KeyCode::O},
{AKEYCODE_P, KeyCode::P},
{AKEYCODE_Q, KeyCode::Q},
{AKEYCODE_R, KeyCode::R},
{AKEYCODE_S, KeyCode::S},
{AKEYCODE_T, KeyCode::T},
{AKEYCODE_U, KeyCode::U},
{AKEYCODE_V, KeyCode::V},
{AKEYCODE_W, KeyCode::W},
{AKEYCODE_X, KeyCode::X},
{AKEYCODE_Y, KeyCode::Y},
{AKEYCODE_Z, KeyCode::Z},
{AKEYCODE_LEFT_BRACKET, KeyCode::LeftBracket},
{AKEYCODE_BACKSLASH, KeyCode::Backslash},
{AKEYCODE_RIGHT_BRACKET, KeyCode::RightBracket},
{AKEYCODE_ESCAPE, KeyCode::Escape},
{AKEYCODE_BACK, KeyCode::Back},
{AKEYCODE_ENTER, KeyCode::Enter},
{AKEYCODE_TAB, KeyCode::Tab},
{AKEYCODE_DEL, KeyCode::Backspace},
{AKEYCODE_INSERT, KeyCode::Insert},
{AKEYCODE_DEL, KeyCode::DelKey},
{AKEYCODE_SYSTEM_NAVIGATION_RIGHT, KeyCode::Right},
{AKEYCODE_SYSTEM_NAVIGATION_LEFT, KeyCode::Left},
{AKEYCODE_SYSTEM_NAVIGATION_DOWN, KeyCode::Down},
{AKEYCODE_SYSTEM_NAVIGATION_UP, KeyCode::Up},
{AKEYCODE_PAGE_UP, KeyCode::PageUp},
{AKEYCODE_PAGE_DOWN, KeyCode::PageDown},
{AKEYCODE_HOME, KeyCode::Home},
{AKEYCODE_CAPS_LOCK, KeyCode::CapsLock},
{AKEYCODE_SCROLL_LOCK, KeyCode::ScrollLock},
{AKEYCODE_NUM_LOCK, KeyCode::NumLock},
{AKEYCODE_BREAK, KeyCode::Pause},
{AKEYCODE_F1, KeyCode::F1},
{AKEYCODE_F2, KeyCode::F2},
{AKEYCODE_F3, KeyCode::F3},
{AKEYCODE_F4, KeyCode::F4},
{AKEYCODE_F5, KeyCode::F5},
{AKEYCODE_F6, KeyCode::F6},
{AKEYCODE_F7, KeyCode::F7},
{AKEYCODE_F8, KeyCode::F8},
{AKEYCODE_F9, KeyCode::F9},
{AKEYCODE_F10, KeyCode::F10},
{AKEYCODE_F11, KeyCode::F11},
{AKEYCODE_F12, KeyCode::F12},
{AKEYCODE_NUMPAD_0, KeyCode::KP_0},
{AKEYCODE_NUMPAD_1, KeyCode::KP_1},
{AKEYCODE_NUMPAD_2, KeyCode::KP_2},
{AKEYCODE_NUMPAD_3, KeyCode::KP_3},
{AKEYCODE_NUMPAD_4, KeyCode::KP_4},
{AKEYCODE_NUMPAD_5, KeyCode::KP_5},
{AKEYCODE_NUMPAD_6, KeyCode::KP_6},
{AKEYCODE_NUMPAD_7, KeyCode::KP_7},
{AKEYCODE_NUMPAD_8, KeyCode::KP_8},
{AKEYCODE_NUMPAD_9, KeyCode::KP_9},
{AKEYCODE_NUMPAD_DOT, KeyCode::KP_Decimal},
{AKEYCODE_NUMPAD_DIVIDE, KeyCode::KP_Divide},
{AKEYCODE_NUMPAD_MULTIPLY, KeyCode::KP_Multiply},
{AKEYCODE_NUMPAD_SUBTRACT, KeyCode::KP_Subtract},
{AKEYCODE_NUMPAD_ADD, KeyCode::KP_Add},
{AKEYCODE_NUMPAD_ENTER, KeyCode::KP_Enter},
{AKEYCODE_NUMPAD_EQUALS, KeyCode::KP_Equal},
{AKEYCODE_SHIFT_LEFT, KeyCode::LeftShift},
{AKEYCODE_CTRL_LEFT, KeyCode::LeftControl},
{AKEYCODE_ALT_LEFT, KeyCode::LeftAlt},
{AKEYCODE_SHIFT_RIGHT, KeyCode::RightShift},
{AKEYCODE_CTRL_RIGHT, KeyCode::RightControl},
{AKEYCODE_ALT_RIGHT, KeyCode::RightAlt}};
auto key_it = key_lookup.find(key);
if (key_it == key_lookup.end())
{
return KeyCode::Unknown;
}
return key_it->second;
}
inline KeyAction translate_key_action(int action)
{
if (action == AKEY_STATE_DOWN)
{
return KeyAction::Down;
}
else if (action == AKEY_STATE_UP)
{
return KeyAction::Up;
}
return KeyAction::Unknown;
}
inline MouseButton translate_mouse_button(int button)
{
if (button < 3)
{
return static_cast<MouseButton>(button);
}
return MouseButton::Unknown;
}
inline MouseAction translate_mouse_action(int action)
{
if (action == AMOTION_EVENT_ACTION_DOWN)
{
return MouseAction::Down;
}
else if (action == AMOTION_EVENT_ACTION_UP)
{
return MouseAction::Up;
}
else if (action == AMOTION_EVENT_ACTION_MOVE)
{
return MouseAction::Move;
}
return MouseAction::Unknown;
}
inline TouchAction translate_touch_action(int action)
{
action &= AMOTION_EVENT_ACTION_MASK;
if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_POINTER_DOWN)
{
return TouchAction::Down;
}
else if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_POINTER_UP)
{
return TouchAction::Up;
}
else if (action == AMOTION_EVENT_ACTION_CANCEL)
{
return TouchAction::Cancel;
}
else if (action == AMOTION_EVENT_ACTION_MOVE)
{
return TouchAction::Move;
}
return TouchAction::Unknown;
}
void on_content_rect_changed(GameActivity *activity, const ARect *rect)
{
LOGI("ContentRectChanged: {:p}\n", static_cast<void *>(activity));
struct android_app *app = reinterpret_cast<struct android_app *>(activity->instance);
auto cmd = APP_CMD_CONTENT_RECT_CHANGED;
app->contentRect = *rect;
if (write(app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd))
{
LOGE("Failure writing android_app cmd: {}\n", strerror(errno));
}
}
void on_app_cmd(android_app *app, int32_t cmd)
{
auto platform = reinterpret_cast<AndroidPlatform *>(app->userData);
assert(platform && "Platform is not valid");
switch (cmd)
{
case APP_CMD_INIT_WINDOW:
{
platform->resize(ANativeWindow_getWidth(app->window),
ANativeWindow_getHeight(app->window));
platform->set_surface_ready();
break;
}
case APP_CMD_CONTENT_RECT_CHANGED:
{
// Get the new size
auto width = app->contentRect.right - app->contentRect.left;
auto height = app->contentRect.bottom - app->contentRect.top;
platform->resize(width, height);
break;
}
case APP_CMD_GAINED_FOCUS:
{
platform->set_focus(true);
break;
}
case APP_CMD_LOST_FOCUS:
{
platform->set_focus(false);
break;
}
}
}
bool key_event_filter(const GameActivityKeyEvent *event)
{
if (event->source == AINPUT_SOURCE_KEYBOARD)
{
return true;
}
return false;
}
bool motion_event_filter(const GameActivityMotionEvent *event)
{
if ((event->source == AINPUT_SOURCE_MOUSE) ||
(event->source == AINPUT_SOURCE_TOUCHSCREEN))
{
return true;
}
return false;
}
} // namespace
namespace fs
{
void create_directory(const std::string &path)
{
if (!is_directory(path))
{
mkdir(path.c_str(), 0777);
}
}
} // namespace fs
AndroidPlatform::AndroidPlatform(const PlatformContext &context) :
Platform{context}
{
if (auto *android = dynamic_cast<const AndroidPlatformContext *>(&context))
{
app = android->app;
}
}
ExitCode AndroidPlatform::initialize(const std::vector<Plugin *> &plugins)
{
android_app_set_key_event_filter(app, key_event_filter);
android_app_set_motion_event_filter(app, motion_event_filter);
app->onAppCmd = on_app_cmd;
app->activity->callbacks->onContentRectChanged = on_content_rect_changed;
app->userData = this;
auto code = Platform::initialize(plugins);
if (code != ExitCode::Success)
{
return code;
}
// Wait until the android window is loaded before allowing the app to continue
LOGI("Waiting on window surface to be ready");
do
{
if (!process_android_events(app))
{
// Android requested for the app to close
LOGI("Android app has been destroyed by the OS");
return ExitCode::Close;
}
} while (!surface_ready);
return ExitCode::Success;
}
void AndroidPlatform::create_window(const Window::Properties &properties)
{
// Android window uses native window size
// Required so that the vulkan sample can create a VkSurface
window = std::make_unique<AndroidWindow>(this, app->window, properties);
}
void AndroidPlatform::process_android_input_events(void)
{
auto input_buf = android_app_swap_input_buffers(app);
if (!input_buf)
{
return;
}
if (input_buf->motionEventsCount)
{
for (int idx = 0; idx < input_buf->motionEventsCount; idx++)
{
auto event = &input_buf->motionEvents[idx];
assert((event->source == AINPUT_SOURCE_MOUSE ||
event->source == AINPUT_SOURCE_TOUCHSCREEN) &&
"Invalid motion event source");
std::int32_t action = event->action;
float x = GameActivityPointerAxes_getX(&event->pointers[0]);
float y = GameActivityPointerAxes_getY(&event->pointers[0]);
if (event->source == AINPUT_SOURCE_MOUSE)
{
input_event(MouseButtonInputEvent{
translate_mouse_button(0),
translate_mouse_action(action),
x, y});
}
else if (event->source == AINPUT_SOURCE_TOUCHSCREEN)
{
// Multiple pointers are not supported.
size_t pointer_count = event->pointerCount;
std::int32_t pointer_id = event->pointers[0].id;
input_event(TouchInputEvent{
pointer_id,
pointer_count,
translate_touch_action(action),
x, y});
}
}
android_app_clear_motion_events(input_buf);
}
if (input_buf->keyEventsCount)
{
for (int idx = 0; idx < input_buf->keyEventsCount; idx++)
{
auto event = &input_buf->keyEvents[idx];
assert((event->source == AINPUT_SOURCE_KEYBOARD) &&
"Invalid key event source");
input_event(KeyInputEvent{
translate_key_code(event->keyCode),
translate_key_action(event->action)});
}
android_app_clear_key_events(input_buf);
}
}
void AndroidPlatform::terminate(ExitCode code)
{
switch (code)
{
case ExitCode::Success:
case ExitCode::Close:
log_output.clear();
break;
case ExitCode::FatalError:
send_notification(log_output);
break;
default:
break;
}
while (process_android_events(app))
{
// Process events until app->destroyRequested is set
}
Platform::terminate(code);
}
void AndroidPlatform::send_notification(const std::string &message)
{
JNIEnv *env;
app->activity->vm->AttachCurrentThread(&env, NULL);
jclass cls = env->GetObjectClass(app->activity->javaGameActivity);
jmethodID fatal_error = env->GetMethodID(cls, "fatalError", "(Ljava/lang/String;)V");
env->CallVoidMethod(app->activity->javaGameActivity, fatal_error, env->NewStringUTF(message.c_str()));
app->activity->vm->DetachCurrentThread();
}
void AndroidPlatform::set_surface_ready()
{
surface_ready = true;
}
GameActivity *AndroidPlatform::get_activity()
{
return app->activity;
}
android_app *AndroidPlatform::get_android_app()
{
return app;
}
std::vector<spdlog::sink_ptr> AndroidPlatform::get_platform_sinks()
{
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::android_sink_mt>(PROJECT_NAME));
char timestamp[80];
std::time_t time = std::time(0);
std::tm now = thread_safe_time(time);
std::strftime(timestamp, 80, "%G-%m-%d_%H-%M-%S_log.txt", &now);
log_output = vkb::fs::path::get(vkb::fs::path::Logs) + std::string(timestamp);
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_output, true));
return sinks;
}
} // namespace vkb