init
This commit is contained in:
@@ -0,0 +1,512 @@
|
||||
/* Copyright (c) 2019-2025, 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"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <imgui.h>
|
||||
#include <jni.h>
|
||||
#include <spdlog/sinks/android_sink.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
|
||||
#include "apps.h"
|
||||
#include "common/strings.h"
|
||||
#include "core/util/logging.hpp"
|
||||
#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
|
||||
|
||||
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)
|
||||
{
|
||||
for (auto plugin : plugins)
|
||||
{
|
||||
plugin->clear_platform();
|
||||
}
|
||||
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:
|
||||
{
|
||||
const std::string error_message = "Error! Could not launch selected sample:" + get_last_error();
|
||||
send_notification(error_message);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
while (process_android_events(app))
|
||||
{
|
||||
// Process events until app->destroyRequested is set
|
||||
}
|
||||
|
||||
plugins.clear();
|
||||
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
|
||||
@@ -0,0 +1,103 @@
|
||||
/* Copyright (c) 2019-2024, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <game-activity/native_app_glue/android_native_app_glue.h>
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class AndroidPlatform : public Platform
|
||||
{
|
||||
public:
|
||||
AndroidPlatform(const PlatformContext &context);
|
||||
|
||||
virtual ~AndroidPlatform() = default;
|
||||
|
||||
virtual ExitCode initialize(const std::vector<Plugin *> &plugins) override;
|
||||
|
||||
virtual void terminate(ExitCode code) override;
|
||||
|
||||
/**
|
||||
* @brief Sends a notification in the task bar
|
||||
* @param message The message to display
|
||||
*/
|
||||
void send_notification(const std::string &message);
|
||||
|
||||
/**
|
||||
* @brief Sends an error notification in the task bar
|
||||
* @param message The message to display
|
||||
*/
|
||||
void send_error_notification(const std::string &message);
|
||||
|
||||
android_app *get_android_app();
|
||||
|
||||
GameActivity *get_activity();
|
||||
|
||||
void set_surface_ready();
|
||||
|
||||
void process_android_input_events(void);
|
||||
|
||||
private:
|
||||
virtual void create_window(const Window::Properties &properties) override;
|
||||
|
||||
private:
|
||||
android_app *app{nullptr};
|
||||
|
||||
std::string log_output;
|
||||
|
||||
virtual std::vector<spdlog::sink_ptr> get_platform_sinks() override;
|
||||
|
||||
bool surface_ready{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Process android lifecycle events
|
||||
*
|
||||
* @param app Android app context
|
||||
* @return true Events processed
|
||||
* @return false Program should close
|
||||
*/
|
||||
inline bool process_android_events(android_app *app)
|
||||
{
|
||||
android_poll_source *source;
|
||||
|
||||
int ident;
|
||||
int events;
|
||||
|
||||
while ((ident = ALooper_pollOnce(0, nullptr, &events, (void **) &source)) > ALOOPER_POLL_TIMEOUT)
|
||||
{
|
||||
if (source)
|
||||
{
|
||||
source->process(app, source);
|
||||
}
|
||||
|
||||
if (app->destroyRequested != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (app->userData)
|
||||
{
|
||||
auto platform = reinterpret_cast<AndroidPlatform *>(app->userData);
|
||||
platform->process_android_input_events();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,79 @@
|
||||
/* Copyright (c) 2018-2025, 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_window.h"
|
||||
|
||||
#include "platform/android/android_platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
AndroidWindow::AndroidWindow(AndroidPlatform *platform, ANativeWindow *&window, const Window::Properties &properties) :
|
||||
Window(properties),
|
||||
handle{window},
|
||||
platform{platform}
|
||||
{
|
||||
}
|
||||
|
||||
VkSurfaceKHR AndroidWindow::create_surface(vkb::core::InstanceC &instance)
|
||||
{
|
||||
return create_surface(instance.get_handle(), VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
VkSurfaceKHR AndroidWindow::create_surface(VkInstance instance, VkPhysicalDevice)
|
||||
{
|
||||
if (instance == VK_NULL_HANDLE || !handle || properties.mode == Mode::Headless)
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkSurfaceKHR surface{};
|
||||
|
||||
VkAndroidSurfaceCreateInfoKHR info{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
|
||||
|
||||
info.window = handle;
|
||||
|
||||
VK_CHECK(vkCreateAndroidSurfaceKHR(instance, &info, nullptr, &surface));
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
void AndroidWindow::process_events()
|
||||
{
|
||||
process_android_events(platform->get_android_app());
|
||||
}
|
||||
|
||||
bool AndroidWindow::should_close()
|
||||
{
|
||||
return finish_called ? true : handle == nullptr;
|
||||
}
|
||||
|
||||
void AndroidWindow::close()
|
||||
{
|
||||
GameActivity_finish(platform->get_activity());
|
||||
finish_called = true;
|
||||
}
|
||||
|
||||
float AndroidWindow::get_dpi_factor() const
|
||||
{
|
||||
return AConfiguration_getDensity(platform->get_android_app()->config) / static_cast<float>(ACONFIGURATION_DENSITY_MEDIUM);
|
||||
}
|
||||
|
||||
std::vector<const char *> AndroidWindow::get_required_surface_extensions() const
|
||||
{
|
||||
return {VK_KHR_ANDROID_SURFACE_EXTENSION_NAME};
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Copyright (c) 2018-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <game-activity/native_app_glue/android_native_app_glue.h>
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "platform/window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class AndroidPlatform;
|
||||
|
||||
/**
|
||||
* @brief Wrapper for a ANativeWindow, handles the window behaviour (including headless mode on Android)
|
||||
* This class should not be responsible for destroying the underlying data it points to
|
||||
*/
|
||||
class AndroidWindow : public Window
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param platform The platform this window is created for
|
||||
* @param window A reference to the location of the Android native window
|
||||
* @param properties Window configuration
|
||||
*/
|
||||
AndroidWindow(AndroidPlatform *platform, ANativeWindow *&window, const Window::Properties &properties);
|
||||
|
||||
virtual ~AndroidWindow() = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a Vulkan surface to the native window
|
||||
* If headless, this will return VK_NULL_HANDLE
|
||||
*/
|
||||
virtual VkSurfaceKHR create_surface(vkb::core::InstanceC &instance) override;
|
||||
|
||||
/**
|
||||
* @brief Creates a Vulkan surface to the native window
|
||||
* If headless, this will return nullptr
|
||||
*/
|
||||
virtual VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice physical_device) override;
|
||||
|
||||
virtual void process_events() override;
|
||||
|
||||
virtual bool should_close() override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
virtual float get_dpi_factor() const override;
|
||||
|
||||
std::vector<const char *> get_required_surface_extensions() const override;
|
||||
|
||||
private:
|
||||
AndroidPlatform *platform;
|
||||
|
||||
// Handle to the android window
|
||||
ANativeWindow *&handle;
|
||||
|
||||
bool finish_called{false};
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,87 @@
|
||||
/* Copyright (c) 2019-2025, 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 "application.h"
|
||||
|
||||
#include "core/util/logging.hpp"
|
||||
#include "platform/window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
Application::Application() :
|
||||
name{"Sample Name"}
|
||||
{
|
||||
}
|
||||
|
||||
bool Application::prepare(const ApplicationOptions &options)
|
||||
{
|
||||
assert(options.window != nullptr && "Window must be valid");
|
||||
|
||||
auto &_debug_info = get_debug_info();
|
||||
_debug_info.insert<field::MinMax, float>("fps", fps);
|
||||
_debug_info.insert<field::MinMax, float>("frame_time", frame_time);
|
||||
|
||||
lock_simulation_speed = options.benchmark_enabled;
|
||||
window = options.window;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::finish()
|
||||
{
|
||||
}
|
||||
|
||||
bool Application::resize(const uint32_t /*width*/, const uint32_t /*height*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::input_event(const InputEvent &input_event)
|
||||
{
|
||||
}
|
||||
|
||||
Drawer *Application::get_drawer()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Application::update(float delta_time)
|
||||
{
|
||||
fps = 1.0f / delta_time;
|
||||
frame_time = delta_time * 1000.0f;
|
||||
}
|
||||
|
||||
void Application::update_overlay(float delta_time, const std::function<void()> &additional_ui)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &Application::get_name() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
void Application::set_name(const std::string &name_)
|
||||
{
|
||||
name = name_;
|
||||
}
|
||||
|
||||
DebugInfo &Application::get_debug_info()
|
||||
{
|
||||
return debug_info;
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,169 @@
|
||||
/* Copyright (c) 2019-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "debug_info.h"
|
||||
#include "drawer.h"
|
||||
#include "platform/configuration.h"
|
||||
#include "platform/input_events.h"
|
||||
#include "timer.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class Window;
|
||||
|
||||
struct ApplicationOptions
|
||||
{
|
||||
bool benchmark_enabled{false};
|
||||
Window *window{nullptr};
|
||||
};
|
||||
|
||||
class Application
|
||||
{
|
||||
public:
|
||||
Application();
|
||||
|
||||
virtual ~Application() = default;
|
||||
|
||||
/**
|
||||
* @brief Prepares the application for execution
|
||||
*/
|
||||
virtual bool prepare(const ApplicationOptions &options);
|
||||
|
||||
/**
|
||||
* @brief Updates the application
|
||||
* @param delta_time The time since the last update
|
||||
*/
|
||||
virtual void update(float delta_time);
|
||||
|
||||
/**
|
||||
* @brief Main loop sample overlay events
|
||||
* @param delta_time The time taken since the last frame
|
||||
* @param additional_ui Function that implements an additional Gui
|
||||
*/
|
||||
virtual void update_overlay(
|
||||
float delta_time, const std::function<void()> &additional_ui = []() {});
|
||||
|
||||
/**
|
||||
* @brief Handles cleaning up the application
|
||||
*/
|
||||
virtual void finish();
|
||||
|
||||
/**
|
||||
* @brief Handles resizing of the window
|
||||
* @param width New width of the window
|
||||
* @param height New height of the window
|
||||
*/
|
||||
virtual bool resize(const uint32_t width, const uint32_t height);
|
||||
|
||||
/**
|
||||
* @brief Handles input events of the window
|
||||
* @param input_event The input event object
|
||||
*/
|
||||
virtual void input_event(const InputEvent &input_event);
|
||||
|
||||
/**
|
||||
* @brief Returns the drawer object for the sample
|
||||
*/
|
||||
virtual Drawer *get_drawer();
|
||||
|
||||
const std::string &get_name() const;
|
||||
|
||||
void set_name(const std::string &name);
|
||||
|
||||
DebugInfo &get_debug_info();
|
||||
|
||||
inline bool should_close() const
|
||||
{
|
||||
return requested_close;
|
||||
}
|
||||
|
||||
// request the app to close
|
||||
// does not guarantee that the app will close immediately
|
||||
inline void close()
|
||||
{
|
||||
requested_close = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the shading language to be used for this sample (glsl, hlsl)
|
||||
* @param language The shading language that the sample will use
|
||||
*/
|
||||
static void set_shading_language(const vkb::ShadingLanguage language);
|
||||
|
||||
/**
|
||||
* @brief Returns the selected shading language to be used for this sample (glsl, hlsl)
|
||||
*/
|
||||
static vkb::ShadingLanguage get_shading_language();
|
||||
|
||||
/**
|
||||
* @brief Returns the default folder for the currently selected shading language
|
||||
*/
|
||||
static std::string get_shader_folder();
|
||||
|
||||
protected:
|
||||
float fps{0.0f};
|
||||
|
||||
float frame_time{0.0f}; // In ms
|
||||
|
||||
uint32_t frame_count{0};
|
||||
|
||||
uint32_t last_frame_count{0};
|
||||
|
||||
bool lock_simulation_speed{false};
|
||||
|
||||
Window *window{nullptr};
|
||||
|
||||
private:
|
||||
std::string name{};
|
||||
|
||||
// The debug info of the app
|
||||
DebugInfo debug_info{};
|
||||
|
||||
bool requested_close{false};
|
||||
|
||||
/** @brief Used to select between different shader languages, static so it can be changed from a plugin */
|
||||
inline static vkb::ShadingLanguage shading_language{vkb::ShadingLanguage::GLSL};
|
||||
};
|
||||
|
||||
inline void Application::set_shading_language(const vkb::ShadingLanguage language)
|
||||
{
|
||||
shading_language = language;
|
||||
}
|
||||
|
||||
inline vkb::ShadingLanguage Application::get_shading_language()
|
||||
{
|
||||
return shading_language;
|
||||
}
|
||||
|
||||
inline std::string Application::get_shader_folder()
|
||||
{
|
||||
switch (shading_language)
|
||||
{
|
||||
case ShadingLanguage::HLSL:
|
||||
return "hlsl";
|
||||
case ShadingLanguage::SLANG:
|
||||
return "slang";
|
||||
default:
|
||||
return "glsl";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,106 @@
|
||||
/* Copyright (c) 2019, 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 "configuration.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
BoolSetting::BoolSetting(bool &handle, bool value) :
|
||||
handle{handle},
|
||||
value{value}
|
||||
{
|
||||
}
|
||||
|
||||
void BoolSetting::set()
|
||||
{
|
||||
handle = value;
|
||||
}
|
||||
|
||||
std::type_index BoolSetting::get_type()
|
||||
{
|
||||
return typeid(BoolSetting);
|
||||
}
|
||||
|
||||
IntSetting::IntSetting(int &handle, int value) :
|
||||
handle{handle},
|
||||
value{value}
|
||||
{
|
||||
}
|
||||
|
||||
void IntSetting::set()
|
||||
{
|
||||
handle = value;
|
||||
}
|
||||
|
||||
std::type_index IntSetting::get_type()
|
||||
{
|
||||
return typeid(IntSetting);
|
||||
}
|
||||
|
||||
EmptySetting::EmptySetting()
|
||||
{
|
||||
}
|
||||
|
||||
void EmptySetting::set()
|
||||
{
|
||||
}
|
||||
|
||||
std::type_index EmptySetting::get_type()
|
||||
{
|
||||
return typeid(EmptySetting);
|
||||
}
|
||||
|
||||
void Configuration::set()
|
||||
{
|
||||
for (auto pair : current_configuration->second)
|
||||
{
|
||||
for (auto setting : pair.second)
|
||||
{
|
||||
setting->set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Configuration::next()
|
||||
{
|
||||
if (configs.size() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
current_configuration++;
|
||||
|
||||
if (current_configuration == configs.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Configuration::reset()
|
||||
{
|
||||
current_configuration = configs.begin();
|
||||
}
|
||||
|
||||
void Configuration::insert_setting(uint32_t config_index, std::unique_ptr<Setting> setting)
|
||||
{
|
||||
settings.push_back(std::move(setting));
|
||||
configs[config_index][settings.back()->get_type()].push_back(settings.back().get());
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,143 @@
|
||||
/* Copyright (c) 2019-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class Setting
|
||||
{
|
||||
public:
|
||||
Setting() = default;
|
||||
|
||||
Setting(Setting &&other) = default;
|
||||
|
||||
virtual ~Setting()
|
||||
{}
|
||||
|
||||
virtual void set() = 0;
|
||||
|
||||
virtual std::type_index get_type() = 0;
|
||||
};
|
||||
|
||||
class BoolSetting : public Setting
|
||||
{
|
||||
public:
|
||||
BoolSetting(bool &handle, bool value);
|
||||
|
||||
virtual void set() override;
|
||||
|
||||
virtual std::type_index get_type() override;
|
||||
|
||||
private:
|
||||
bool &handle;
|
||||
|
||||
bool value;
|
||||
};
|
||||
|
||||
class IntSetting : public Setting
|
||||
{
|
||||
public:
|
||||
IntSetting(int &handle, int value);
|
||||
|
||||
virtual void set() override;
|
||||
|
||||
virtual std::type_index get_type() override;
|
||||
|
||||
private:
|
||||
int &handle;
|
||||
|
||||
int value;
|
||||
};
|
||||
|
||||
class EmptySetting : public Setting
|
||||
{
|
||||
public:
|
||||
EmptySetting();
|
||||
|
||||
virtual void set() override;
|
||||
|
||||
virtual std::type_index get_type() override;
|
||||
};
|
||||
|
||||
using ConfigMap = std::map<uint32_t, std::unordered_map<std::type_index, std::vector<Setting *>>>;
|
||||
|
||||
/**
|
||||
* @brief A class that contains configuration data for a sample.
|
||||
*/
|
||||
class Configuration
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
Configuration() = default;
|
||||
|
||||
/**
|
||||
* @brief Configures the settings in the current config
|
||||
*/
|
||||
void set();
|
||||
|
||||
/**
|
||||
* @brief Increments the configuration count
|
||||
* @returns True if the current configuration iterator was incremented
|
||||
*/
|
||||
bool next();
|
||||
|
||||
/**
|
||||
* @brief Resets the configuration to beginning
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Inserts a setting into the current configuration
|
||||
* @param config_index The configuration to insert the setting into
|
||||
* @param setting A setting to be inserted into the configuration
|
||||
*/
|
||||
void insert_setting(uint32_t config_index, std::unique_ptr<Setting> setting);
|
||||
|
||||
/**
|
||||
* @brief Inserts a setting into the current configuration
|
||||
* @param config_index The configuration to insert the setting into
|
||||
* @param args A parameter pack containing the parameters to initialize a setting object
|
||||
*/
|
||||
template <class T, class... A>
|
||||
void insert(uint32_t config_index, A &&... args)
|
||||
{
|
||||
static_assert(std::is_base_of<Setting, T>::value,
|
||||
"T is not a type of setting.");
|
||||
|
||||
insert_setting(config_index, std::make_unique<T>(args...));
|
||||
}
|
||||
|
||||
protected:
|
||||
ConfigMap configs;
|
||||
|
||||
std::vector<std::unique_ptr<Setting>> settings;
|
||||
|
||||
ConfigMap::iterator current_configuration;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,415 @@
|
||||
/* Copyright (c) 2019-2025, 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 "glfw_window.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GLFW/glfw3native.h>
|
||||
#include <fmt/format.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
#include "core/util/logging.hpp"
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void error_callback(int error, const char *description)
|
||||
{
|
||||
LOGE("GLFW Error (code {}): {}", error, description);
|
||||
}
|
||||
|
||||
void window_close_callback(GLFWwindow *window)
|
||||
{
|
||||
glfwSetWindowShouldClose(window, GLFW_TRUE);
|
||||
}
|
||||
|
||||
void window_size_callback(GLFWwindow *window, int width, int height)
|
||||
{
|
||||
if (auto platform = reinterpret_cast<Platform *>(glfwGetWindowUserPointer(window)))
|
||||
{
|
||||
platform->resize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void window_focus_callback(GLFWwindow *window, int focused)
|
||||
{
|
||||
if (auto platform = reinterpret_cast<Platform *>(glfwGetWindowUserPointer(window)))
|
||||
{
|
||||
platform->set_focus(focused);
|
||||
}
|
||||
}
|
||||
|
||||
inline KeyCode translate_key_code(int key)
|
||||
{
|
||||
static const std::unordered_map<int, KeyCode> key_lookup =
|
||||
{
|
||||
{GLFW_KEY_SPACE, KeyCode::Space},
|
||||
{GLFW_KEY_APOSTROPHE, KeyCode::Apostrophe},
|
||||
{GLFW_KEY_COMMA, KeyCode::Comma},
|
||||
{GLFW_KEY_MINUS, KeyCode::Minus},
|
||||
{GLFW_KEY_PERIOD, KeyCode::Period},
|
||||
{GLFW_KEY_SLASH, KeyCode::Slash},
|
||||
{GLFW_KEY_0, KeyCode::_0},
|
||||
{GLFW_KEY_1, KeyCode::_1},
|
||||
{GLFW_KEY_2, KeyCode::_2},
|
||||
{GLFW_KEY_3, KeyCode::_3},
|
||||
{GLFW_KEY_4, KeyCode::_4},
|
||||
{GLFW_KEY_5, KeyCode::_5},
|
||||
{GLFW_KEY_6, KeyCode::_6},
|
||||
{GLFW_KEY_7, KeyCode::_7},
|
||||
{GLFW_KEY_8, KeyCode::_8},
|
||||
{GLFW_KEY_9, KeyCode::_9},
|
||||
{GLFW_KEY_SEMICOLON, KeyCode::Semicolon},
|
||||
{GLFW_KEY_EQUAL, KeyCode::Equal},
|
||||
{GLFW_KEY_A, KeyCode::A},
|
||||
{GLFW_KEY_B, KeyCode::B},
|
||||
{GLFW_KEY_C, KeyCode::C},
|
||||
{GLFW_KEY_D, KeyCode::D},
|
||||
{GLFW_KEY_E, KeyCode::E},
|
||||
{GLFW_KEY_F, KeyCode::F},
|
||||
{GLFW_KEY_G, KeyCode::G},
|
||||
{GLFW_KEY_H, KeyCode::H},
|
||||
{GLFW_KEY_I, KeyCode::I},
|
||||
{GLFW_KEY_J, KeyCode::J},
|
||||
{GLFW_KEY_K, KeyCode::K},
|
||||
{GLFW_KEY_L, KeyCode::L},
|
||||
{GLFW_KEY_M, KeyCode::M},
|
||||
{GLFW_KEY_N, KeyCode::N},
|
||||
{GLFW_KEY_O, KeyCode::O},
|
||||
{GLFW_KEY_P, KeyCode::P},
|
||||
{GLFW_KEY_Q, KeyCode::Q},
|
||||
{GLFW_KEY_R, KeyCode::R},
|
||||
{GLFW_KEY_S, KeyCode::S},
|
||||
{GLFW_KEY_T, KeyCode::T},
|
||||
{GLFW_KEY_U, KeyCode::U},
|
||||
{GLFW_KEY_V, KeyCode::V},
|
||||
{GLFW_KEY_W, KeyCode::W},
|
||||
{GLFW_KEY_X, KeyCode::X},
|
||||
{GLFW_KEY_Y, KeyCode::Y},
|
||||
{GLFW_KEY_Z, KeyCode::Z},
|
||||
{GLFW_KEY_LEFT_BRACKET, KeyCode::LeftBracket},
|
||||
{GLFW_KEY_BACKSLASH, KeyCode::Backslash},
|
||||
{GLFW_KEY_RIGHT_BRACKET, KeyCode::RightBracket},
|
||||
{GLFW_KEY_GRAVE_ACCENT, KeyCode::GraveAccent},
|
||||
{GLFW_KEY_ESCAPE, KeyCode::Escape},
|
||||
{GLFW_KEY_ENTER, KeyCode::Enter},
|
||||
{GLFW_KEY_TAB, KeyCode::Tab},
|
||||
{GLFW_KEY_BACKSPACE, KeyCode::Backspace},
|
||||
{GLFW_KEY_INSERT, KeyCode::Insert},
|
||||
{GLFW_KEY_DELETE, KeyCode::DelKey},
|
||||
{GLFW_KEY_RIGHT, KeyCode::Right},
|
||||
{GLFW_KEY_LEFT, KeyCode::Left},
|
||||
{GLFW_KEY_DOWN, KeyCode::Down},
|
||||
{GLFW_KEY_UP, KeyCode::Up},
|
||||
{GLFW_KEY_PAGE_UP, KeyCode::PageUp},
|
||||
{GLFW_KEY_PAGE_DOWN, KeyCode::PageDown},
|
||||
{GLFW_KEY_HOME, KeyCode::Home},
|
||||
{GLFW_KEY_END, KeyCode::End},
|
||||
{GLFW_KEY_CAPS_LOCK, KeyCode::CapsLock},
|
||||
{GLFW_KEY_SCROLL_LOCK, KeyCode::ScrollLock},
|
||||
{GLFW_KEY_NUM_LOCK, KeyCode::NumLock},
|
||||
{GLFW_KEY_PRINT_SCREEN, KeyCode::PrintScreen},
|
||||
{GLFW_KEY_PAUSE, KeyCode::Pause},
|
||||
{GLFW_KEY_F1, KeyCode::F1},
|
||||
{GLFW_KEY_F2, KeyCode::F2},
|
||||
{GLFW_KEY_F3, KeyCode::F3},
|
||||
{GLFW_KEY_F4, KeyCode::F4},
|
||||
{GLFW_KEY_F5, KeyCode::F5},
|
||||
{GLFW_KEY_F6, KeyCode::F6},
|
||||
{GLFW_KEY_F7, KeyCode::F7},
|
||||
{GLFW_KEY_F8, KeyCode::F8},
|
||||
{GLFW_KEY_F9, KeyCode::F9},
|
||||
{GLFW_KEY_F10, KeyCode::F10},
|
||||
{GLFW_KEY_F11, KeyCode::F11},
|
||||
{GLFW_KEY_F12, KeyCode::F12},
|
||||
{GLFW_KEY_KP_0, KeyCode::KP_0},
|
||||
{GLFW_KEY_KP_1, KeyCode::KP_1},
|
||||
{GLFW_KEY_KP_2, KeyCode::KP_2},
|
||||
{GLFW_KEY_KP_3, KeyCode::KP_3},
|
||||
{GLFW_KEY_KP_4, KeyCode::KP_4},
|
||||
{GLFW_KEY_KP_5, KeyCode::KP_5},
|
||||
{GLFW_KEY_KP_6, KeyCode::KP_6},
|
||||
{GLFW_KEY_KP_7, KeyCode::KP_7},
|
||||
{GLFW_KEY_KP_8, KeyCode::KP_8},
|
||||
{GLFW_KEY_KP_9, KeyCode::KP_9},
|
||||
{GLFW_KEY_KP_DECIMAL, KeyCode::KP_Decimal},
|
||||
{GLFW_KEY_KP_DIVIDE, KeyCode::KP_Divide},
|
||||
{GLFW_KEY_KP_MULTIPLY, KeyCode::KP_Multiply},
|
||||
{GLFW_KEY_KP_SUBTRACT, KeyCode::KP_Subtract},
|
||||
{GLFW_KEY_KP_ADD, KeyCode::KP_Add},
|
||||
{GLFW_KEY_KP_ENTER, KeyCode::KP_Enter},
|
||||
{GLFW_KEY_KP_EQUAL, KeyCode::KP_Equal},
|
||||
{GLFW_KEY_LEFT_SHIFT, KeyCode::LeftShift},
|
||||
{GLFW_KEY_LEFT_CONTROL, KeyCode::LeftControl},
|
||||
{GLFW_KEY_LEFT_ALT, KeyCode::LeftAlt},
|
||||
{GLFW_KEY_RIGHT_SHIFT, KeyCode::RightShift},
|
||||
{GLFW_KEY_RIGHT_CONTROL, KeyCode::RightControl},
|
||||
{GLFW_KEY_RIGHT_ALT, 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 == GLFW_PRESS)
|
||||
{
|
||||
return KeyAction::Down;
|
||||
}
|
||||
else if (action == GLFW_RELEASE)
|
||||
{
|
||||
return KeyAction::Up;
|
||||
}
|
||||
else if (action == GLFW_REPEAT)
|
||||
{
|
||||
return KeyAction::Repeat;
|
||||
}
|
||||
|
||||
return KeyAction::Unknown;
|
||||
}
|
||||
|
||||
void key_callback(GLFWwindow *window, int key, int /*scancode*/, int action, int /*mods*/)
|
||||
{
|
||||
KeyCode key_code = translate_key_code(key);
|
||||
KeyAction key_action = translate_key_action(action);
|
||||
|
||||
if (auto platform = reinterpret_cast<Platform *>(glfwGetWindowUserPointer(window)))
|
||||
{
|
||||
platform->input_event(KeyInputEvent{key_code, key_action});
|
||||
}
|
||||
}
|
||||
|
||||
inline MouseButton translate_mouse_button(int button)
|
||||
{
|
||||
if (button < GLFW_MOUSE_BUTTON_6)
|
||||
{
|
||||
return static_cast<MouseButton>(button);
|
||||
}
|
||||
|
||||
return MouseButton::Unknown;
|
||||
}
|
||||
|
||||
inline MouseAction translate_mouse_action(int action)
|
||||
{
|
||||
if (action == GLFW_PRESS)
|
||||
{
|
||||
return MouseAction::Down;
|
||||
}
|
||||
else if (action == GLFW_RELEASE)
|
||||
{
|
||||
return MouseAction::Up;
|
||||
}
|
||||
|
||||
return MouseAction::Unknown;
|
||||
}
|
||||
|
||||
void cursor_position_callback(GLFWwindow *window, double xpos, double ypos)
|
||||
{
|
||||
if (auto *platform = reinterpret_cast<Platform *>(glfwGetWindowUserPointer(window)))
|
||||
{
|
||||
platform->input_event(MouseButtonInputEvent{
|
||||
MouseButton::Unknown,
|
||||
MouseAction::Move,
|
||||
static_cast<float>(xpos),
|
||||
static_cast<float>(ypos)});
|
||||
}
|
||||
}
|
||||
|
||||
void mouse_button_callback(GLFWwindow *window, int button, int action, int /*mods*/)
|
||||
{
|
||||
MouseAction mouse_action = translate_mouse_action(action);
|
||||
|
||||
if (auto *platform = reinterpret_cast<Platform *>(glfwGetWindowUserPointer(window)))
|
||||
{
|
||||
double xpos, ypos;
|
||||
glfwGetCursorPos(window, &xpos, &ypos);
|
||||
|
||||
platform->input_event(MouseButtonInputEvent{
|
||||
translate_mouse_button(button),
|
||||
mouse_action,
|
||||
static_cast<float>(xpos),
|
||||
static_cast<float>(ypos)});
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
GlfwWindow::GlfwWindow(Platform *platform, const Window::Properties &properties) :
|
||||
Window(properties)
|
||||
{
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
glfwInitHint(GLFW_X11_XCB_VULKAN_SURFACE, false);
|
||||
#endif
|
||||
|
||||
if (!glfwInit())
|
||||
{
|
||||
throw std::runtime_error("GLFW couldn't be initialized.");
|
||||
}
|
||||
|
||||
glfwSetErrorCallback(error_callback);
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
|
||||
switch (properties.mode)
|
||||
{
|
||||
case Window::Mode::Fullscreen:
|
||||
{
|
||||
auto *monitor = glfwGetPrimaryMonitor();
|
||||
const auto *mode = glfwGetVideoMode(monitor);
|
||||
handle = glfwCreateWindow(mode->width, mode->height, properties.title.c_str(), monitor, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
case Window::Mode::FullscreenBorderless:
|
||||
{
|
||||
auto *monitor = glfwGetPrimaryMonitor();
|
||||
const auto *mode = glfwGetVideoMode(monitor);
|
||||
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
|
||||
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
|
||||
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
|
||||
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
|
||||
handle = glfwCreateWindow(mode->width, mode->height, properties.title.c_str(), monitor, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
case Window::Mode::FullscreenStretch:
|
||||
{
|
||||
throw std::runtime_error("Cannot support stretch mode on this platform.");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
handle = glfwCreateWindow(properties.extent.width, properties.extent.height, properties.title.c_str(), NULL, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
resize(Extent{properties.extent.width, properties.extent.height});
|
||||
|
||||
if (!handle)
|
||||
{
|
||||
throw std::runtime_error("Couldn't create glfw window.");
|
||||
}
|
||||
|
||||
glfwSetWindowUserPointer(handle, platform);
|
||||
|
||||
glfwSetWindowCloseCallback(handle, window_close_callback);
|
||||
glfwSetWindowSizeCallback(handle, window_size_callback);
|
||||
glfwSetWindowFocusCallback(handle, window_focus_callback);
|
||||
glfwSetKeyCallback(handle, key_callback);
|
||||
glfwSetCursorPosCallback(handle, cursor_position_callback);
|
||||
glfwSetMouseButtonCallback(handle, mouse_button_callback);
|
||||
|
||||
glfwSetInputMode(handle, GLFW_STICKY_KEYS, 1);
|
||||
glfwSetInputMode(handle, GLFW_STICKY_MOUSE_BUTTONS, 1);
|
||||
}
|
||||
|
||||
GlfwWindow::~GlfwWindow()
|
||||
{
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
VkSurfaceKHR GlfwWindow::create_surface(vkb::core::InstanceC &instance)
|
||||
{
|
||||
return create_surface(instance.get_handle(), VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
VkSurfaceKHR GlfwWindow::create_surface(VkInstance instance, VkPhysicalDevice)
|
||||
{
|
||||
if (instance == VK_NULL_HANDLE || !handle)
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
VkResult errCode = glfwCreateWindowSurface(instance, handle, NULL, &surface);
|
||||
|
||||
if (errCode != VK_SUCCESS)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
bool GlfwWindow::should_close()
|
||||
{
|
||||
return glfwWindowShouldClose(handle);
|
||||
}
|
||||
|
||||
void GlfwWindow::process_events()
|
||||
{
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
void GlfwWindow::close()
|
||||
{
|
||||
glfwSetWindowShouldClose(handle, GLFW_TRUE);
|
||||
}
|
||||
|
||||
/// @brief It calculates the dpi factor using the density from GLFW physical size
|
||||
/// <a href="https://www.glfw.org/docs/latest/monitor_guide.html#monitor_size">GLFW docs for dpi</a>
|
||||
float GlfwWindow::get_dpi_factor() const
|
||||
{
|
||||
auto primary_monitor = glfwGetPrimaryMonitor();
|
||||
auto vidmode = glfwGetVideoMode(primary_monitor);
|
||||
|
||||
int width_mm, height_mm;
|
||||
glfwGetMonitorPhysicalSize(primary_monitor, &width_mm, &height_mm);
|
||||
|
||||
// As suggested by the GLFW monitor guide
|
||||
static const float inch_to_mm = 25.0f;
|
||||
static const float win_base_density = 96.0f;
|
||||
|
||||
auto dpi = static_cast<uint32_t>(vidmode->width / (width_mm / inch_to_mm));
|
||||
auto dpi_factor = dpi / win_base_density;
|
||||
return dpi_factor;
|
||||
}
|
||||
|
||||
float GlfwWindow::get_content_scale_factor() const
|
||||
{
|
||||
int fb_width, fb_height;
|
||||
glfwGetFramebufferSize(handle, &fb_width, &fb_height);
|
||||
int win_width, win_height;
|
||||
glfwGetWindowSize(handle, &win_width, &win_height);
|
||||
|
||||
// We could return a 2D result here instead of a scalar,
|
||||
// but non-uniform scaling is very unlikely, and would
|
||||
// require significantly more changes in the IMGUI integration
|
||||
return static_cast<float>(fb_width) / win_width;
|
||||
}
|
||||
|
||||
std::vector<const char *> GlfwWindow::get_required_surface_extensions() const
|
||||
{
|
||||
uint32_t glfw_extension_count{0};
|
||||
const char **names = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
|
||||
return {names, names + glfw_extension_count};
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,58 @@
|
||||
/* Copyright (c) 2019-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "platform/window.h"
|
||||
|
||||
struct GLFWwindow;
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class Platform;
|
||||
|
||||
/**
|
||||
* @brief An implementation of GLFW, inheriting the behaviour of the Window interface
|
||||
*/
|
||||
class GlfwWindow : public Window
|
||||
{
|
||||
public:
|
||||
GlfwWindow(Platform *platform, const Window::Properties &properties);
|
||||
|
||||
virtual ~GlfwWindow();
|
||||
|
||||
VkSurfaceKHR create_surface(vkb::core::InstanceC &instance) override;
|
||||
|
||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice physical_device) override;
|
||||
|
||||
bool should_close() override;
|
||||
|
||||
void process_events() override;
|
||||
|
||||
void close() override;
|
||||
|
||||
float get_dpi_factor() const override;
|
||||
|
||||
float get_content_scale_factor() const override;
|
||||
|
||||
std::vector<const char *> get_required_surface_extensions() const override;
|
||||
|
||||
private:
|
||||
GLFWwindow *handle = nullptr;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Copyright (c) 2018-2025, 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 "headless_window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
HeadlessWindow::HeadlessWindow(const Window::Properties &properties) :
|
||||
Window(properties)
|
||||
{
|
||||
}
|
||||
|
||||
VkSurfaceKHR HeadlessWindow::create_surface(vkb::core::InstanceC &instance)
|
||||
{
|
||||
return create_surface(instance.get_handle(), VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
VkSurfaceKHR HeadlessWindow::create_surface(VkInstance instance, VkPhysicalDevice)
|
||||
{
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
|
||||
if (instance)
|
||||
{
|
||||
VkHeadlessSurfaceCreateInfoEXT info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT;
|
||||
|
||||
VK_CHECK(vkCreateHeadlessSurfaceEXT(instance, &info, VK_NULL_HANDLE, &surface));
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
bool HeadlessWindow::should_close()
|
||||
{
|
||||
return closed;
|
||||
}
|
||||
|
||||
void HeadlessWindow::close()
|
||||
{
|
||||
closed = true;
|
||||
}
|
||||
|
||||
float HeadlessWindow::get_dpi_factor() const
|
||||
{
|
||||
// This factor is used for scaling UI elements, so return 1.0f (1 x n = n)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
std::vector<const char *> HeadlessWindow::get_required_surface_extensions() const
|
||||
{
|
||||
return {VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME};
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,59 @@
|
||||
/* Copyright (c) 2018-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
/**
|
||||
* @brief Surface-less implementation of a Window using VK_EXT_headless_surface.
|
||||
* A surface and swapchain are still created but the the present operation resolves to a no op.
|
||||
* Useful for testing and benchmarking in CI environments.
|
||||
*/
|
||||
class HeadlessWindow : public Window
|
||||
{
|
||||
public:
|
||||
HeadlessWindow(const Window::Properties &properties);
|
||||
|
||||
virtual ~HeadlessWindow() = default;
|
||||
|
||||
/**
|
||||
* @brief A direct window doesn't have a surface
|
||||
* @returns VK_NULL_HANDLE
|
||||
*/
|
||||
VkSurfaceKHR create_surface(vkb::core::InstanceC &instance) override;
|
||||
|
||||
/**
|
||||
* @brief A direct window doesn't have a surface
|
||||
* @returns nullptr
|
||||
*/
|
||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice physical_device) override;
|
||||
|
||||
bool should_close() override;
|
||||
|
||||
void close() override;
|
||||
|
||||
float get_dpi_factor() const override;
|
||||
|
||||
std::vector<const char *> get_required_surface_extensions() const override;
|
||||
|
||||
private:
|
||||
bool closed{false};
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,112 @@
|
||||
/* Copyright (c) 2019-2021, 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 "input_events.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
InputEvent::InputEvent(EventSource source) :
|
||||
source{source}
|
||||
{
|
||||
}
|
||||
|
||||
EventSource InputEvent::get_source() const
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
KeyInputEvent::KeyInputEvent(KeyCode code, KeyAction action) :
|
||||
InputEvent{EventSource::Keyboard},
|
||||
code{code},
|
||||
action{action}
|
||||
{
|
||||
}
|
||||
|
||||
KeyCode KeyInputEvent::get_code() const
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
KeyAction KeyInputEvent::get_action() const
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
MouseButtonInputEvent::MouseButtonInputEvent(MouseButton button, MouseAction action, float pos_x, float pos_y) :
|
||||
InputEvent{EventSource::Mouse},
|
||||
button{button},
|
||||
action{action},
|
||||
pos_x{pos_x},
|
||||
pos_y{pos_y}
|
||||
{
|
||||
}
|
||||
|
||||
MouseButton MouseButtonInputEvent::get_button() const
|
||||
{
|
||||
return button;
|
||||
}
|
||||
|
||||
MouseAction MouseButtonInputEvent::get_action() const
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
float MouseButtonInputEvent::get_pos_x() const
|
||||
{
|
||||
return pos_x;
|
||||
}
|
||||
|
||||
float MouseButtonInputEvent::get_pos_y() const
|
||||
{
|
||||
return pos_y;
|
||||
}
|
||||
|
||||
TouchInputEvent::TouchInputEvent(int32_t pointer_id, std::size_t touch_points, TouchAction action, float pos_x, float pos_y) :
|
||||
InputEvent{EventSource::Touchscreen},
|
||||
action{action},
|
||||
pointer_id{pointer_id},
|
||||
touch_points{touch_points},
|
||||
pos_x{pos_x},
|
||||
pos_y{pos_y}
|
||||
{
|
||||
}
|
||||
|
||||
TouchAction TouchInputEvent::get_action() const
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
int32_t TouchInputEvent::get_pointer_id() const
|
||||
{
|
||||
return pointer_id;
|
||||
}
|
||||
|
||||
std::size_t TouchInputEvent::get_touch_points() const
|
||||
{
|
||||
return touch_points;
|
||||
}
|
||||
|
||||
float TouchInputEvent::get_pos_x() const
|
||||
{
|
||||
return pos_x;
|
||||
}
|
||||
|
||||
float TouchInputEvent::get_pos_y() const
|
||||
{
|
||||
return pos_y;
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,254 @@
|
||||
/* Copyright (c) 2019-2021, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class Platform;
|
||||
|
||||
enum class EventSource
|
||||
{
|
||||
Keyboard,
|
||||
Mouse,
|
||||
Touchscreen
|
||||
};
|
||||
|
||||
class InputEvent
|
||||
{
|
||||
public:
|
||||
InputEvent(EventSource source);
|
||||
|
||||
EventSource get_source() const;
|
||||
|
||||
private:
|
||||
EventSource source;
|
||||
};
|
||||
|
||||
enum class KeyCode
|
||||
{
|
||||
Unknown,
|
||||
Space,
|
||||
Apostrophe, /* ' */
|
||||
Comma, /* , */
|
||||
Minus, /* - */
|
||||
Period, /* . */
|
||||
Slash, /* / */
|
||||
_0,
|
||||
_1,
|
||||
_2,
|
||||
_3,
|
||||
_4,
|
||||
_5,
|
||||
_6,
|
||||
_7,
|
||||
_8,
|
||||
_9,
|
||||
Semicolon, /* ; */
|
||||
Equal, /* = */
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
LeftBracket, /* [ */
|
||||
Backslash, /* \ */
|
||||
RightBracket, /* ] */
|
||||
GraveAccent, /* ` */
|
||||
Escape,
|
||||
Enter,
|
||||
Tab,
|
||||
Backspace,
|
||||
Insert,
|
||||
DelKey,
|
||||
Right,
|
||||
Left,
|
||||
Down,
|
||||
Up,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
Back,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
NumLock,
|
||||
PrintScreen,
|
||||
Pause,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
KP_0,
|
||||
KP_1,
|
||||
KP_2,
|
||||
KP_3,
|
||||
KP_4,
|
||||
KP_5,
|
||||
KP_6,
|
||||
KP_7,
|
||||
KP_8,
|
||||
KP_9,
|
||||
KP_Decimal,
|
||||
KP_Divide,
|
||||
KP_Multiply,
|
||||
KP_Subtract,
|
||||
KP_Add,
|
||||
KP_Enter,
|
||||
KP_Equal,
|
||||
LeftShift,
|
||||
LeftControl,
|
||||
LeftAlt,
|
||||
RightShift,
|
||||
RightControl,
|
||||
RightAlt
|
||||
};
|
||||
|
||||
enum class KeyAction
|
||||
{
|
||||
Down,
|
||||
Up,
|
||||
Repeat,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class KeyInputEvent : public InputEvent
|
||||
{
|
||||
public:
|
||||
KeyInputEvent(KeyCode code, KeyAction action);
|
||||
|
||||
KeyCode get_code() const;
|
||||
|
||||
KeyAction get_action() const;
|
||||
|
||||
private:
|
||||
KeyCode code;
|
||||
|
||||
KeyAction action;
|
||||
};
|
||||
|
||||
enum class MouseButton
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Back,
|
||||
Forward,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class MouseAction
|
||||
{
|
||||
Down,
|
||||
Up,
|
||||
Move,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class MouseButtonInputEvent : public InputEvent
|
||||
{
|
||||
public:
|
||||
MouseButtonInputEvent(MouseButton button, MouseAction action, float pos_x, float pos_y);
|
||||
|
||||
MouseButton get_button() const;
|
||||
|
||||
MouseAction get_action() const;
|
||||
|
||||
float get_pos_x() const;
|
||||
|
||||
float get_pos_y() const;
|
||||
|
||||
private:
|
||||
MouseButton button;
|
||||
|
||||
MouseAction action;
|
||||
|
||||
float pos_x;
|
||||
|
||||
float pos_y;
|
||||
};
|
||||
|
||||
enum class TouchAction
|
||||
{
|
||||
Down,
|
||||
Up,
|
||||
Move,
|
||||
Cancel,
|
||||
PointerDown,
|
||||
PointerUp,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class TouchInputEvent : public InputEvent
|
||||
{
|
||||
public:
|
||||
TouchInputEvent(int32_t pointer_id, size_t pointer_count, TouchAction action, float pos_x, float pos_y);
|
||||
|
||||
TouchAction get_action() const;
|
||||
|
||||
int32_t get_pointer_id() const;
|
||||
|
||||
size_t get_touch_points() const;
|
||||
|
||||
float get_pos_x() const;
|
||||
|
||||
float get_pos_y() const;
|
||||
|
||||
private:
|
||||
TouchAction action;
|
||||
|
||||
int32_t pointer_id;
|
||||
|
||||
size_t touch_points;
|
||||
|
||||
float pos_x;
|
||||
|
||||
float pos_y;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,38 @@
|
||||
/* Copyright (c) 2023-2024, Holochip Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
|
||||
class IosPlatform : public Platform
|
||||
{
|
||||
public:
|
||||
IosPlatform(const PlatformContext &context);
|
||||
|
||||
virtual ~IosPlatform() = default;
|
||||
|
||||
virtual const char *get_surface_extension();
|
||||
void* view;
|
||||
|
||||
protected:
|
||||
virtual void create_window(const Window::Properties &properties) override;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,90 @@
|
||||
/* Copyright (c) 2023-2024, Holochip Inc.
|
||||
*
|
||||
* 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 "ios_platform.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "ios/context.hpp"
|
||||
#include "platform/headless_window.h"
|
||||
#include "platform/ios/ios_window.h"
|
||||
|
||||
VKBP_DISABLE_WARNINGS()
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
VKBP_ENABLE_WARNINGS()
|
||||
|
||||
#import <objc/message.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
// This is equivalent to creating a @class with one public variable named 'window'.
|
||||
struct AppDel
|
||||
{
|
||||
Class isa;
|
||||
|
||||
id window;
|
||||
};
|
||||
|
||||
#ifndef VK_MVK_MACOS_SURFACE_EXTENSION_NAME
|
||||
# define VK_MVK_MACOS_SURFACE_EXTENSION_NAME "VK_MVK_macos_surface"
|
||||
#endif
|
||||
|
||||
#ifndef VK_EXT_METAL_SURFACE_EXTENSION_NAME
|
||||
# define VK_EXT_METAL_SURFACE_EXTENSION_NAME "VK_EXT_metal_surface"
|
||||
#endif
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
namespace
|
||||
{
|
||||
inline const std::string get_temp_path_from_environment()
|
||||
{
|
||||
std::string temp_path = "/tmp/";
|
||||
|
||||
if (const char *env_ptr = std::getenv("TMPDIR"))
|
||||
{
|
||||
temp_path = std::string(env_ptr) + "/";
|
||||
}
|
||||
|
||||
return temp_path;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
IosPlatform::IosPlatform(const PlatformContext &context)
|
||||
: Platform{context}
|
||||
{
|
||||
IosPlatformContext * cont = (IosPlatformContext*)&context;
|
||||
view = cont->view;
|
||||
}
|
||||
|
||||
const char *IosPlatform::get_surface_extension()
|
||||
{
|
||||
return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
|
||||
}
|
||||
|
||||
void IosPlatform::create_window(const Window::Properties &properties)
|
||||
{
|
||||
if (properties.mode == vkb::Window::Mode::Headless)
|
||||
{
|
||||
window = std::make_unique<HeadlessWindow>(properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
window = std::make_unique<IosWindow>(this, properties);
|
||||
((IosWindow*)window.get())->set_vulkan_view((VulkanView*) view);
|
||||
}
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,81 @@
|
||||
/* Copyright (c) 2023-2025, Holochip Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "platform/window.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface VulkanView : UIView
|
||||
@end
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class IosPlatform;
|
||||
|
||||
/**
|
||||
* @brief Wrapper for a ANativeWindow, handles the window behaviour (including headless mode on Android)
|
||||
* This class should not be responsible for destroying the underlying data it points to
|
||||
*/
|
||||
class IosWindow : public Window
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param platform The platform this window is created for
|
||||
* @param properties Window configuration
|
||||
*/
|
||||
IosWindow(IosPlatform *platform, const Window::Properties &properties);
|
||||
|
||||
~IosWindow() override = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a Vulkan surface to the native window
|
||||
* If headless, this will return VK_NULL_HANDLE
|
||||
*/
|
||||
VkSurfaceKHR create_surface(vkb::core::InstanceC &instance) override;
|
||||
|
||||
/**
|
||||
* @brief Creates a Vulkan surface to the native window
|
||||
* If headless, this will return nullptr
|
||||
*/
|
||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice physical_device) override;
|
||||
|
||||
void process_events() override;
|
||||
|
||||
bool should_close() override;
|
||||
|
||||
void close() override;
|
||||
|
||||
float get_dpi_factor() const override;
|
||||
|
||||
float get_content_scale_factor() const override;
|
||||
|
||||
std::vector<const char *> get_required_surface_extensions() const override;
|
||||
|
||||
VulkanView * get_vulkan_view() {return view;}
|
||||
void set_vulkan_view(VulkanView* _view) { view = _view; }
|
||||
|
||||
private:
|
||||
VulkanView * view;
|
||||
IosPlatform *platform;
|
||||
|
||||
bool finish_called{false};
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,88 @@
|
||||
/* Copyright (c) 2023-2025, Holochip Inc.
|
||||
*
|
||||
* 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 "ios_window.h"
|
||||
#include <UIKit/UIScreen.h>
|
||||
#include <UIKit/UIWindow.h>
|
||||
|
||||
#include "platform/ios/ios_platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
IosWindow::IosWindow(IosPlatform *platform, const Window::Properties &properties) :
|
||||
Window(properties),
|
||||
platform{platform}
|
||||
{
|
||||
}
|
||||
|
||||
VkSurfaceKHR IosWindow::create_surface(vkb::core::InstanceC &instance)
|
||||
{
|
||||
return create_surface(instance.get_handle(), VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
VkSurfaceKHR IosWindow::create_surface(VkInstance instance, VkPhysicalDevice)
|
||||
{
|
||||
if (instance == VK_NULL_HANDLE || properties.mode == Mode::Headless)
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkSurfaceKHR surface{};
|
||||
|
||||
VkMetalSurfaceCreateInfoEXT info{VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT};
|
||||
|
||||
view = (VulkanView*)platform->view;
|
||||
info.pLayer = (CAMetalLayer *) [view layer];
|
||||
|
||||
VK_CHECK(vkCreateMetalSurfaceEXT(instance, &info, nullptr, &surface));
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
void IosWindow::process_events()
|
||||
{
|
||||
}
|
||||
|
||||
bool IosWindow::should_close()
|
||||
{
|
||||
return finish_called;
|
||||
}
|
||||
|
||||
void IosWindow::close()
|
||||
{
|
||||
finish_called = true;
|
||||
}
|
||||
|
||||
float IosWindow::get_dpi_factor() const
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float IosWindow::get_content_scale_factor() const
|
||||
{
|
||||
return [[UIScreen mainScreen] nativeScale];
|
||||
}
|
||||
|
||||
std::vector<const char *> IosWindow::get_required_surface_extensions() const
|
||||
{
|
||||
return {VK_EXT_METAL_SURFACE_EXTENSION_NAME};
|
||||
}
|
||||
} // namespace vkb
|
||||
|
||||
@implementation VulkanView
|
||||
+(Class) layerClass { return [CAMetalLayer class]; }
|
||||
@end
|
||||
@@ -0,0 +1,571 @@
|
||||
/* Copyright (c) 2019-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* 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 "platform.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <spdlog/async_logger.h>
|
||||
#include <spdlog/details/thread_pool.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
#include "core/util/logging.hpp"
|
||||
#include "force_close/force_close.h"
|
||||
#include "platform/plugins/plugin.h"
|
||||
#include "vulkan_sample.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
const uint32_t Platform::MIN_WINDOW_WIDTH = 420;
|
||||
const uint32_t Platform::MIN_WINDOW_HEIGHT = 320;
|
||||
|
||||
Platform::Platform(const PlatformContext &context)
|
||||
{
|
||||
arguments = context.arguments();
|
||||
}
|
||||
|
||||
ExitCode Platform::initialize(const std::vector<Plugin *> &plugins_)
|
||||
{
|
||||
plugins = plugins_;
|
||||
|
||||
auto sinks = get_platform_sinks();
|
||||
|
||||
auto logger = std::make_shared<spdlog::logger>("logger", sinks.begin(), sinks.end());
|
||||
|
||||
#ifdef VKB_DEBUG
|
||||
logger->set_level(spdlog::level::debug);
|
||||
#else
|
||||
logger->set_level(spdlog::level::info);
|
||||
#endif
|
||||
|
||||
logger->set_pattern(LOGGER_FORMAT);
|
||||
spdlog::set_default_logger(logger);
|
||||
|
||||
LOGI("Logger initialized");
|
||||
|
||||
// To get the error messages formatted as we like them to have, exit after initializing the logger, earliest
|
||||
if (arguments.empty())
|
||||
{
|
||||
return ExitCode::NoSample;
|
||||
}
|
||||
else if (std::ranges::any_of(arguments, [](auto const &arg) { return arg == "-h" || arg == "--help"; }))
|
||||
{
|
||||
return ExitCode::Help;
|
||||
}
|
||||
|
||||
for (auto const &plugin : plugins)
|
||||
{
|
||||
plugin->set_platform(this);
|
||||
for (auto const &command : plugin->get_commands())
|
||||
{
|
||||
auto [it, inserted] = command_map.insert(std::make_pair(command.first, plugin));
|
||||
if (!inserted)
|
||||
{
|
||||
LOGE("Command \"{}\" from plugin \"{}\" is already listed for plugin \"{}\"!", command.first, plugin->get_name(), it->second->get_name());
|
||||
}
|
||||
}
|
||||
for (auto const &option : plugin->get_options())
|
||||
{
|
||||
auto [it, inserted] = option_map.insert(std::make_pair(option.first, plugin));
|
||||
if (!inserted)
|
||||
{
|
||||
LOGE("Option \"{}\" from plugin \"{}\" is already listed for plugin \"{}\"!", option.first, plugin->get_name(), it->second->get_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<std::string> argumentDeque(arguments.begin(), arguments.end());
|
||||
|
||||
// the arguments have to start with a command
|
||||
auto commandIt = command_map.find(argumentDeque[0]);
|
||||
if (commandIt == command_map.end())
|
||||
{
|
||||
LOGE("Command \"{}\" is unknown!", argumentDeque[0]);
|
||||
return ExitCode::Help;
|
||||
}
|
||||
else if (commandIt->second->handle_command(argumentDeque))
|
||||
{
|
||||
register_hooks(commandIt->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGE("Command \"{}\" advertised by plugin \"{}\" was not handled!", argumentDeque[0], commandIt->second->get_name());
|
||||
return ExitCode::Help;
|
||||
}
|
||||
// and then there are options only
|
||||
while (!argumentDeque.empty())
|
||||
{
|
||||
if (argumentDeque[0].substr(0, 2) != "--")
|
||||
{
|
||||
LOGE("Option \"{}\" does not start with \"--\"!", argumentDeque[0]);
|
||||
return ExitCode::Help;
|
||||
}
|
||||
auto optionIt = option_map.find(argumentDeque[0].substr(2));
|
||||
if (commandIt == command_map.end())
|
||||
{
|
||||
LOGE("Option \"{}\" is unknown!", argumentDeque[0]);
|
||||
return ExitCode::Help;
|
||||
}
|
||||
else if (optionIt->second->handle_option(argumentDeque))
|
||||
{
|
||||
register_hooks(optionIt->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGE("Option \"{}\" advertised by plugin \"{}\" was not handled!", argumentDeque[0], optionIt->second->get_name());
|
||||
return ExitCode::Help;
|
||||
}
|
||||
}
|
||||
// Now that all options are handled, trigger the command
|
||||
commandIt->second->trigger_command();
|
||||
|
||||
// Platform has been closed by a plugins initialization phase
|
||||
if (close_requested)
|
||||
{
|
||||
return ExitCode::Close;
|
||||
}
|
||||
|
||||
if (!app_requested())
|
||||
{
|
||||
return ExitCode::NoSample;
|
||||
}
|
||||
|
||||
create_window(window_properties);
|
||||
|
||||
if (!window)
|
||||
{
|
||||
LOGE("Window creation failed!");
|
||||
return ExitCode::FatalError;
|
||||
}
|
||||
|
||||
return ExitCode::Success;
|
||||
}
|
||||
|
||||
void Platform::register_hooks(Plugin *plugin)
|
||||
{
|
||||
auto &plugin_hooks = plugin->get_hooks();
|
||||
for (auto hook : plugin_hooks)
|
||||
{
|
||||
auto it = hooks.find(hook);
|
||||
|
||||
if (it == hooks.end())
|
||||
{
|
||||
auto r = hooks.emplace(hook, std::vector<Plugin *>{});
|
||||
assert(r.second);
|
||||
it = r.first;
|
||||
}
|
||||
|
||||
if (std::ranges::none_of(it->second, [plugin](auto p) { return p == plugin; }))
|
||||
{
|
||||
it->second.emplace_back(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
if (std::ranges::none_of(active_plugins, [plugin](auto p) { return p == plugin; }))
|
||||
{
|
||||
active_plugins.emplace_back(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
ExitCode Platform::main_loop_frame()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load the requested app
|
||||
if (app_requested())
|
||||
{
|
||||
if (!start_app())
|
||||
{
|
||||
throw std::runtime_error("Failed to load requested application");
|
||||
}
|
||||
|
||||
// Compensate for load times of the app by rendering the first frame pre-emptively
|
||||
timer.tick<Timer::Seconds>();
|
||||
active_app->update(0.01667f);
|
||||
}
|
||||
|
||||
if (!active_app)
|
||||
{
|
||||
return ExitCode::NoSample;
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
if (active_app->should_close())
|
||||
{
|
||||
std::string id = active_app->get_name();
|
||||
on_app_close(id);
|
||||
active_app->finish();
|
||||
}
|
||||
|
||||
window->process_events();
|
||||
|
||||
if (window->should_close() || close_requested)
|
||||
{
|
||||
return ExitCode::Close;
|
||||
}
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
LOGE("Error Message: {}", e.what());
|
||||
LOGE("Failed when running application {}", active_app->get_name());
|
||||
|
||||
on_app_error(active_app->get_name());
|
||||
|
||||
if (app_requested())
|
||||
{
|
||||
LOGI("Attempting to load next application");
|
||||
}
|
||||
else
|
||||
{
|
||||
set_last_error(e.what());
|
||||
return ExitCode::FatalError;
|
||||
}
|
||||
}
|
||||
|
||||
return ExitCode::Success;
|
||||
}
|
||||
|
||||
ExitCode Platform::main_loop()
|
||||
{
|
||||
ExitCode exit_code = ExitCode::Success;
|
||||
while (exit_code == ExitCode::Success)
|
||||
{
|
||||
exit_code = main_loop_frame();
|
||||
}
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
void Platform::update()
|
||||
{
|
||||
auto delta_time = static_cast<float>(timer.tick<Timer::Seconds>());
|
||||
|
||||
if (focused || always_render)
|
||||
{
|
||||
on_update(delta_time);
|
||||
|
||||
if (fixed_simulation_fps)
|
||||
{
|
||||
delta_time = simulation_frame_time;
|
||||
}
|
||||
|
||||
active_app->update_overlay(delta_time, [=, this]() {
|
||||
on_update_ui_overlay(*active_app->get_drawer());
|
||||
});
|
||||
active_app->update(delta_time);
|
||||
|
||||
if (auto *app = dynamic_cast<VulkanSampleCpp *>(active_app.get()))
|
||||
{
|
||||
if (app->has_render_context())
|
||||
{
|
||||
on_post_draw(reinterpret_cast<vkb::RenderContext &>(app->get_render_context()));
|
||||
}
|
||||
}
|
||||
else if (auto *app = dynamic_cast<VulkanSampleC *>(active_app.get()))
|
||||
{
|
||||
if (app->has_render_context())
|
||||
{
|
||||
on_post_draw(app->get_render_context());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Platform::terminate(ExitCode code)
|
||||
{
|
||||
if (code == ExitCode::Help)
|
||||
{
|
||||
LOGI("");
|
||||
LOGI("\tVulkan Samples");
|
||||
LOGI("");
|
||||
LOGI("\t\tA collection of samples to demonstrate the Vulkan best practice.");
|
||||
LOGI("");
|
||||
LOGI("\tUsage: vulkan_samples [OPTIONS]");
|
||||
LOGI("");
|
||||
LOGI("\t\tOptions:");
|
||||
LOGI("\t\t\t-h,--help Print this help message and exit");
|
||||
|
||||
// determine the width for the commands/options
|
||||
size_t width = 4; // minimal width for "help"
|
||||
for (auto plugin : plugins)
|
||||
{
|
||||
for (auto const &command : plugin->get_commands())
|
||||
{
|
||||
width = std::max(width, command.first.length());
|
||||
}
|
||||
for (auto const &option : plugin->get_options())
|
||||
{
|
||||
width = std::max(width, option.first.length());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto plugin : plugins)
|
||||
{
|
||||
plugin->log_help(width + 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (code == ExitCode::NoSample)
|
||||
{
|
||||
LOGI("");
|
||||
LOGI("No sample was requested or the selected sample does not exist");
|
||||
LOGI("");
|
||||
LOGI("To run a specific sample use the \"sample\" argument, e.g.");
|
||||
LOGI("");
|
||||
LOGI("\tvulkan_samples sample hello_triangle");
|
||||
LOGI("");
|
||||
LOGI("To get a list of available samples, use the \"samples\" argument")
|
||||
LOGI("To get a list of available command line options, use the \"-h\" or \"--help\" argument");
|
||||
LOGI("");
|
||||
}
|
||||
|
||||
if (active_app)
|
||||
{
|
||||
std::string id = active_app->get_name();
|
||||
on_app_close(id);
|
||||
active_app->finish();
|
||||
}
|
||||
|
||||
active_app.reset();
|
||||
window.reset();
|
||||
|
||||
spdlog::drop_all();
|
||||
|
||||
on_platform_close();
|
||||
|
||||
#ifdef PLATFORM__WINDOWS
|
||||
// Halt on all unsuccessful exit codes unless ForceClose is in use
|
||||
if (code != ExitCode::Success && !using_plugin<::plugins::ForceClose>())
|
||||
{
|
||||
std::cout << "Press return to continue";
|
||||
std::cin.get();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Platform::close()
|
||||
{
|
||||
if (window)
|
||||
{
|
||||
window->close();
|
||||
}
|
||||
|
||||
// Fallback incase a window is not yet in use
|
||||
close_requested = true;
|
||||
}
|
||||
|
||||
void Platform::force_simulation_fps(float fps)
|
||||
{
|
||||
fixed_simulation_fps = true;
|
||||
simulation_frame_time = 1 / fps;
|
||||
}
|
||||
|
||||
void Platform::force_render(bool should_always_render)
|
||||
{
|
||||
always_render = should_always_render;
|
||||
}
|
||||
|
||||
void Platform::disable_input_processing()
|
||||
{
|
||||
process_input_events = false;
|
||||
}
|
||||
|
||||
void Platform::set_focus(bool _focused)
|
||||
{
|
||||
focused = _focused;
|
||||
}
|
||||
|
||||
void Platform::set_window_properties(const Window::OptionalProperties &properties)
|
||||
{
|
||||
window_properties.title = properties.title.has_value() ? properties.title.value() : window_properties.title;
|
||||
window_properties.mode = properties.mode.has_value() ? properties.mode.value() : window_properties.mode;
|
||||
window_properties.resizable = properties.resizable.has_value() ? properties.resizable.value() : window_properties.resizable;
|
||||
window_properties.vsync = properties.vsync.has_value() ? properties.vsync.value() : window_properties.vsync;
|
||||
window_properties.extent.width = properties.extent.width.has_value() ? properties.extent.width.value() : window_properties.extent.width;
|
||||
window_properties.extent.height = properties.extent.height.has_value() ? properties.extent.height.value() : window_properties.extent.height;
|
||||
}
|
||||
|
||||
std::string &Platform::get_last_error()
|
||||
{
|
||||
return last_error;
|
||||
}
|
||||
|
||||
Application &Platform::get_app()
|
||||
{
|
||||
assert(active_app && "Application is not valid");
|
||||
return *active_app;
|
||||
}
|
||||
|
||||
Application &Platform::get_app() const
|
||||
{
|
||||
assert(active_app && "Application is not valid");
|
||||
return *active_app;
|
||||
}
|
||||
|
||||
Window &Platform::get_window()
|
||||
{
|
||||
return *window;
|
||||
}
|
||||
|
||||
void Platform::set_last_error(const std::string &error)
|
||||
{
|
||||
last_error = error;
|
||||
}
|
||||
|
||||
std::vector<spdlog::sink_ptr> Platform::get_platform_sinks()
|
||||
{
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
|
||||
return sinks;
|
||||
}
|
||||
|
||||
bool Platform::app_requested()
|
||||
{
|
||||
return requested_app != nullptr;
|
||||
}
|
||||
|
||||
void Platform::request_application(const apps::AppInfo *app)
|
||||
{
|
||||
requested_app = app;
|
||||
}
|
||||
|
||||
bool Platform::start_app()
|
||||
{
|
||||
auto *requested_app_info = requested_app;
|
||||
// Reset early incase error in preparation stage
|
||||
requested_app = nullptr;
|
||||
|
||||
if (active_app)
|
||||
{
|
||||
auto execution_time = timer.stop();
|
||||
LOGI("Closing App (Runtime: {:.1f})", execution_time);
|
||||
|
||||
auto app_id = active_app->get_name();
|
||||
|
||||
active_app->finish();
|
||||
}
|
||||
|
||||
active_app = requested_app_info->create();
|
||||
|
||||
if (!active_app)
|
||||
{
|
||||
LOGE("Failed to create a valid vulkan app.");
|
||||
return false;
|
||||
}
|
||||
auto sample_info = static_cast<const apps::SampleInfo *>(requested_app_info);
|
||||
active_app->set_name(sample_info->name);
|
||||
|
||||
if (!active_app->prepare({false, window.get()}))
|
||||
{
|
||||
LOGE("Failed to prepare vulkan app.");
|
||||
return false;
|
||||
}
|
||||
|
||||
on_app_start(requested_app_info->id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Platform::input_event(const InputEvent &input_event)
|
||||
{
|
||||
if (process_input_events && active_app)
|
||||
{
|
||||
active_app->input_event(input_event);
|
||||
}
|
||||
|
||||
if (input_event.get_source() == EventSource::Keyboard)
|
||||
{
|
||||
const auto &key_event = static_cast<const KeyInputEvent &>(input_event);
|
||||
|
||||
if (key_event.get_code() == KeyCode::Back ||
|
||||
key_event.get_code() == KeyCode::Escape)
|
||||
{
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Platform::resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
auto extent = Window::Extent{std::max<uint32_t>(width, MIN_WINDOW_WIDTH), std::max<uint32_t>(height, MIN_WINDOW_HEIGHT)};
|
||||
if ((window) && (width > 0) && (height > 0))
|
||||
{
|
||||
auto actual_extent = window->resize(extent);
|
||||
|
||||
if (active_app)
|
||||
{
|
||||
active_app->resize(actual_extent.width, actual_extent.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define HOOK(enum, func) \
|
||||
static auto res = hooks.find(enum); \
|
||||
if (res != hooks.end()) \
|
||||
{ \
|
||||
for (auto plugin : res->second) \
|
||||
{ \
|
||||
plugin->func; \
|
||||
} \
|
||||
}
|
||||
|
||||
void Platform::on_post_draw(RenderContext &context)
|
||||
{
|
||||
HOOK(Hook::PostDraw, on_post_draw(context));
|
||||
}
|
||||
|
||||
void Platform::on_app_error(const std::string &app_id)
|
||||
{
|
||||
HOOK(Hook::OnAppError, on_app_error(app_id));
|
||||
}
|
||||
|
||||
void Platform::on_update(float delta_time)
|
||||
{
|
||||
HOOK(Hook::OnUpdate, on_update(delta_time));
|
||||
}
|
||||
|
||||
void Platform::on_app_start(const std::string &app_id)
|
||||
{
|
||||
HOOK(Hook::OnAppStart, on_app_start(app_id));
|
||||
}
|
||||
|
||||
void Platform::on_app_close(const std::string &app_id)
|
||||
{
|
||||
HOOK(Hook::OnAppClose, on_app_close(app_id));
|
||||
}
|
||||
|
||||
void Platform::on_platform_close()
|
||||
{
|
||||
HOOK(Hook::OnPlatformClose, on_platform_close());
|
||||
}
|
||||
|
||||
void Platform::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
HOOK(Hook::OnUpdateUi, on_update_ui_overlay(drawer));
|
||||
}
|
||||
|
||||
#undef HOOK
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,202 @@
|
||||
/* Copyright (c) 2019-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <core/platform/context.hpp>
|
||||
|
||||
#include "apps.h"
|
||||
#include "common/optional.h"
|
||||
#include "common/utils.h"
|
||||
#include "common/vk_common.h"
|
||||
#include "platform/application.h"
|
||||
#include "platform/plugins/plugin.h"
|
||||
#include "platform/window.h"
|
||||
#include "rendering/render_context.h"
|
||||
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
# undef Success
|
||||
#endif
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
enum class ExitCode
|
||||
{
|
||||
Success = 0, /* App executed as expected */
|
||||
NoSample, /* App should show help how to run a sample */
|
||||
Help, /* App should show help */
|
||||
Close, /* App has been requested to close at initialization */
|
||||
FatalError /* App encountered an unexpected error */
|
||||
};
|
||||
|
||||
class Platform
|
||||
{
|
||||
public:
|
||||
Platform(const PlatformContext &context);
|
||||
|
||||
virtual ~Platform() = default;
|
||||
|
||||
/**
|
||||
* @brief Initialize the platform
|
||||
* @param plugins plugins available to the platform
|
||||
* @return An exit code representing the outcome of initialization
|
||||
*/
|
||||
virtual ExitCode initialize(const std::vector<Plugin *> &plugins);
|
||||
|
||||
/**
|
||||
* @brief Handles the main update and render loop
|
||||
* @return An exit code representing the outcome of the loop
|
||||
*/
|
||||
ExitCode main_loop();
|
||||
|
||||
/**
|
||||
* @brief Handles the update and render of a frame.
|
||||
* Called either from main_loop(), or from a platform-specific
|
||||
* frame-looping mechanism, typically tied to platform screen refeshes.
|
||||
* @return An exit code representing the outcome of the loop
|
||||
*/
|
||||
ExitCode main_loop_frame();
|
||||
|
||||
/**
|
||||
* @brief Runs the application for one frame
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief Terminates the platform and the application
|
||||
* @param code Determines how the platform should exit
|
||||
*/
|
||||
virtual void terminate(ExitCode code);
|
||||
|
||||
/**
|
||||
* @brief Requests to close the platform at the next available point
|
||||
*/
|
||||
virtual void close();
|
||||
|
||||
std::string &get_last_error();
|
||||
|
||||
virtual void resize(uint32_t width, uint32_t height);
|
||||
|
||||
virtual void input_event(const InputEvent &input_event);
|
||||
|
||||
Window &get_window();
|
||||
|
||||
Application &get_app() const;
|
||||
|
||||
Application &get_app();
|
||||
|
||||
void set_last_error(const std::string &error);
|
||||
|
||||
template <class T>
|
||||
T *get_plugin() const;
|
||||
|
||||
template <class T>
|
||||
bool using_plugin() const;
|
||||
|
||||
void set_focus(bool focused);
|
||||
|
||||
void request_application(const apps::AppInfo *app);
|
||||
|
||||
bool app_requested();
|
||||
|
||||
bool start_app();
|
||||
|
||||
void force_simulation_fps(float fps);
|
||||
|
||||
// Force the application to always render even if it is not in focus
|
||||
void force_render(bool should_always_render);
|
||||
|
||||
void disable_input_processing();
|
||||
|
||||
void set_window_properties(const Window::OptionalProperties &properties);
|
||||
|
||||
void on_post_draw(RenderContext &context);
|
||||
|
||||
static const uint32_t MIN_WINDOW_WIDTH;
|
||||
static const uint32_t MIN_WINDOW_HEIGHT;
|
||||
|
||||
protected:
|
||||
std::vector<Plugin *> active_plugins;
|
||||
|
||||
std::unordered_map<Hook, std::vector<Plugin *>> hooks;
|
||||
|
||||
std::unique_ptr<Window> window{nullptr};
|
||||
|
||||
std::unique_ptr<Application> active_app{nullptr};
|
||||
|
||||
virtual std::vector<spdlog::sink_ptr> get_platform_sinks();
|
||||
|
||||
/**
|
||||
* @brief Handles the creation of the window
|
||||
*
|
||||
* @param properties Preferred window configuration
|
||||
*/
|
||||
virtual void create_window(const Window::Properties &properties) = 0;
|
||||
|
||||
void register_hooks(Plugin *plugin);
|
||||
|
||||
void on_update(float delta_time);
|
||||
void on_app_error(const std::string &app_id);
|
||||
void on_app_start(const std::string &app_id);
|
||||
void on_app_close(const std::string &app_id);
|
||||
void on_platform_close();
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer);
|
||||
|
||||
Window::Properties window_properties; /* Source of truth for window state */
|
||||
bool fixed_simulation_fps{false}; /* Delta time should be fixed with a fabricated value */
|
||||
bool always_render{false}; /* App should always render even if not in focus */
|
||||
float simulation_frame_time = 0.016f; /* A fabricated delta time */
|
||||
bool process_input_events{true}; /* App should continue processing input events */
|
||||
bool focused{true}; /* App is currently in focus at an operating system level */
|
||||
bool close_requested{false}; /* Close requested */
|
||||
|
||||
protected:
|
||||
std::vector<Plugin *> plugins;
|
||||
|
||||
private:
|
||||
Timer timer;
|
||||
|
||||
const apps::AppInfo *requested_app{nullptr};
|
||||
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
std::string last_error;
|
||||
|
||||
std::map<std::string, Plugin *> command_map;
|
||||
std::map<std::string, Plugin *> option_map;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
bool Platform::using_plugin() const
|
||||
{
|
||||
return !plugins::with_tags<T>(active_plugins).empty();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T *Platform::get_plugin() const
|
||||
{
|
||||
assert(using_plugin<T>() && "Plugin is not enabled but was requested");
|
||||
const auto plugins = plugins::with_tags<T>(active_plugins);
|
||||
return dynamic_cast<T *>(plugins[0]);
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Copyright (c) 2020-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* 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 "plugin.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
const std::string &Plugin::get_name() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
const std::string &Plugin::get_description() const
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
void Plugin::log_help(size_t width) const
|
||||
{
|
||||
LOGI("");
|
||||
LOGI("\t{}", name);
|
||||
LOGI("\t\t{}", description);
|
||||
LOGI("");
|
||||
|
||||
if (!commands.empty())
|
||||
{
|
||||
LOGI("\t\tSubcommands:");
|
||||
for (auto const &command : commands)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.setf(std::ios::left);
|
||||
oss.width(width + 2);
|
||||
oss << command.first;
|
||||
LOGI("\t\t\t{}{}", oss.str().c_str(), command.second);
|
||||
}
|
||||
LOGI("");
|
||||
}
|
||||
|
||||
if (!options.empty())
|
||||
{
|
||||
LOGI("\t\tOptions:");
|
||||
for (auto const &option : options)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.setf(std::ios::left);
|
||||
oss.width(width);
|
||||
oss << option.first;
|
||||
LOGI("\t\t\t--{}{}", oss.str().c_str(), option.second);
|
||||
}
|
||||
LOGI("");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,310 @@
|
||||
/* Copyright (c) 2020-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2023-2025, Mobica Limited
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <vector>
|
||||
|
||||
#include "common/tags.h"
|
||||
#include "gui.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class Platform;
|
||||
class RenderContext;
|
||||
class Plugin;
|
||||
|
||||
/**
|
||||
* @brief Tags are used to define a plugins behaviour. This is useful to dictate which plugins will work together
|
||||
* and which will not without directly specifying an exclusion or inclusion list. Tags are struct types so that they can
|
||||
* be used in the tagging system (See plugin implementation).
|
||||
*
|
||||
* Entrypoint - An entrypoint is a starting point for the application that will load a vkb::Application (see start_app)
|
||||
* FullControl - The plugin wants full control over how the application executes. Stopping plugins will be ignored (see batch_mode)
|
||||
* Stopping - The plugin will stop the app through its own mechanism (see stop_after)
|
||||
* Passive - These plugins provide non intrusive behaviour (see fps_logger)
|
||||
*/
|
||||
namespace tags
|
||||
{
|
||||
struct Entrypoint
|
||||
{};
|
||||
struct FullControl
|
||||
{};
|
||||
struct Stopping
|
||||
{};
|
||||
struct Passive
|
||||
{};
|
||||
} // namespace tags
|
||||
|
||||
/**
|
||||
* @brief Associate how plugins can interact with each other. This interoperability is decided by comparing tags of different plugins. The plugins inclusion and exclusion lists are populated by this function
|
||||
*
|
||||
* @param plugins A list of plugins which are used together
|
||||
* @return std::vector<Plugin *> A list of plugins which are used together
|
||||
*/
|
||||
std::vector<Plugin *> associate_plugins(const std::vector<Plugin *> &plugins);
|
||||
|
||||
/**
|
||||
* @brief Hooks are points in the project that an plugin can subscribe too. These can be expanded on to implement more behaviour in the future
|
||||
*
|
||||
* Update - Executed at each update() loop
|
||||
* OnAppStart - Executed when an app starts
|
||||
* OnAppClose - Executed when an app closes
|
||||
* OnPlatformClose - Executed when the platform closes (End off the apps lifecycle)
|
||||
*/
|
||||
enum class Hook
|
||||
{
|
||||
OnUpdate,
|
||||
OnAppStart,
|
||||
OnAppClose,
|
||||
OnAppError,
|
||||
OnPlatformClose,
|
||||
PostDraw,
|
||||
OnUpdateUi
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Plugins are used to define custom behaviour. This allows the addition of features without directly
|
||||
* interfering with the applications core implementation
|
||||
*/
|
||||
class Plugin
|
||||
{
|
||||
public:
|
||||
Plugin(const std::string name,
|
||||
const std::string description,
|
||||
std::vector<std::pair<std::string, std::string>> const &commands = {},
|
||||
std::vector<std::pair<std::string, std::string>> const &options = {}) :
|
||||
name{name}, description{description}, commands{commands}, options{options} {};
|
||||
|
||||
virtual ~Plugin() = default;
|
||||
|
||||
/**
|
||||
* @brief Return a list of hooks that an plugin wants to subscribe to
|
||||
*
|
||||
* @return Hooks that the plugin wants to use
|
||||
*/
|
||||
virtual const std::vector<Hook> &get_hooks() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Called when an application has been updated
|
||||
*
|
||||
* @param delta_time The time taken to compute a frame
|
||||
*/
|
||||
virtual void on_update(float delta_time) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called when an app has started
|
||||
*
|
||||
* @param app_id The ID of the app
|
||||
*/
|
||||
virtual void on_app_start(const std::string &app_id) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called when an app has been closed
|
||||
*
|
||||
* @param app_id The ID of the app
|
||||
*/
|
||||
virtual void on_app_close(const std::string &app_id) = 0;
|
||||
|
||||
/**
|
||||
* @brief Handle when an application errors
|
||||
*
|
||||
* @param app_id The ID of the app which errored
|
||||
*/
|
||||
virtual void on_app_error(const std::string &app_id) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called when the platform has been requested to close
|
||||
*/
|
||||
virtual void on_platform_close() = 0;
|
||||
|
||||
/**
|
||||
* @brief Post Draw
|
||||
*/
|
||||
virtual void on_post_draw(RenderContext &context) = 0;
|
||||
|
||||
/**
|
||||
* @brief Allows to add a UI to a sample
|
||||
*
|
||||
* @param drawer The object that is responsible for drawing the overlay
|
||||
*/
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) = 0;
|
||||
|
||||
const std::string &get_name() const;
|
||||
const std::string &get_description() const;
|
||||
|
||||
/**
|
||||
* @brief Test whether the plugin contains a given tag
|
||||
*
|
||||
* @tparam C the tag to check for
|
||||
* @return true tag present
|
||||
* @return false tag not present
|
||||
*/
|
||||
template <typename C>
|
||||
bool has_tag() const
|
||||
{
|
||||
return has_tag(Tag<C>::ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tests whether the plugins contains multiple tags
|
||||
*
|
||||
* @tparam C A set of tags
|
||||
* @return true Contains all tags
|
||||
* @return false Does not contain all tags
|
||||
*/
|
||||
template <typename... C>
|
||||
bool has_tags() const
|
||||
{
|
||||
std::vector<TagID> query = {Tag<C>::ID...};
|
||||
bool res = true;
|
||||
for (auto id : query)
|
||||
{
|
||||
res &= has_tag(id);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Implemented by plugin base to return if the plugin contains a tag
|
||||
*
|
||||
* @param id The tag id of a tag
|
||||
* @return true contains tag
|
||||
* @return false does not contain tag
|
||||
*/
|
||||
virtual bool has_tag(TagID id) const = 0;
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> const &get_commands() const
|
||||
{
|
||||
return commands;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> const &get_options() const
|
||||
{
|
||||
return options;
|
||||
}
|
||||
|
||||
virtual bool handle_command(std::deque<std::string> &arguments) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool handle_option(std::deque<std::string> &arguments)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void trigger_command()
|
||||
{}
|
||||
|
||||
void log_help(size_t width) const;
|
||||
|
||||
void set_platform(Platform *platform)
|
||||
{
|
||||
assert(!this->platform && platform);
|
||||
this->platform = platform;
|
||||
}
|
||||
|
||||
void clear_platform()
|
||||
{
|
||||
platform = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
Platform *platform = nullptr;
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::vector<std::pair<std::string, std::string>> commands;
|
||||
std::vector<std::pair<std::string, std::string>> options;
|
||||
};
|
||||
|
||||
/**
|
||||
* The following section provides helper functions for filtering containers of plugins
|
||||
*/
|
||||
namespace plugins
|
||||
{
|
||||
/**
|
||||
* @brief Get all plugins with tags
|
||||
* Plugin must include one or more tags
|
||||
*
|
||||
* @tparam TAGS Tags that an plugin must contain
|
||||
* @param domain The list of plugins to query
|
||||
* @return const std::vector<Plugin *> A list of plugins containing one or more TAGS
|
||||
*/
|
||||
template <typename... TAGS>
|
||||
const std::vector<Plugin *> with_tags(const std::vector<Plugin *> &domain = {})
|
||||
{
|
||||
std::vector<TagID> tags = {Tag<TAGS>::ID...};
|
||||
std::vector<Plugin *> compatable;
|
||||
for (auto ext : domain)
|
||||
{
|
||||
assert(ext != nullptr);
|
||||
|
||||
bool has_one = false;
|
||||
for (auto t : tags)
|
||||
{
|
||||
has_one |= ext->has_tag(t);
|
||||
}
|
||||
|
||||
if (has_one)
|
||||
{
|
||||
compatable.push_back(ext);
|
||||
}
|
||||
}
|
||||
return compatable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get all plugins without the given tags
|
||||
* Plugin must not include one or more tags
|
||||
* Essentially the opoposite of plugins::with_tags<...TAGS>()
|
||||
*
|
||||
* @tparam TAGS Tags that an plugin must not contain
|
||||
* @param domain The list of plugins to query
|
||||
* @return const std::vector<Plugin *> A list of plugins containing one or more TAGS
|
||||
*/
|
||||
template <typename... TAGS>
|
||||
const std::vector<Plugin *> without_tags(const std::vector<Plugin *> &domain = {})
|
||||
{
|
||||
std::vector<TagID> tags = {Tag<TAGS>::ID...};
|
||||
std::vector<Plugin *> compatable;
|
||||
for (auto ext : domain)
|
||||
{
|
||||
assert(ext != nullptr);
|
||||
|
||||
bool has_any = false;
|
||||
for (auto t : tags)
|
||||
{
|
||||
has_any |= ext->has_tag(t);
|
||||
}
|
||||
|
||||
if (!has_any)
|
||||
{
|
||||
compatable.push_back(ext);
|
||||
}
|
||||
}
|
||||
return compatable;
|
||||
}
|
||||
} // namespace plugins
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Copyright (c) 2020-2025, Arm Limited and Contributors
|
||||
* Copyright (c) 2023-2025, Mobica Limited
|
||||
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/tags.h"
|
||||
#include "platform/platform.h"
|
||||
#include "plugin.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
/**
|
||||
* @brief PluginBase is the base class that plugins inherit from. The class enforces the use of tags when creating new plugins.
|
||||
* For method information see Plugin
|
||||
*/
|
||||
template <typename... TAGS>
|
||||
class PluginBase : public Plugin, public Tag<TAGS...>
|
||||
{
|
||||
public:
|
||||
PluginBase(const std::string name, const std::string description, const std::vector<Hook> &hooks = {}, std::vector<std::pair<std::string, std::string>> const &commands = {}, std::vector<std::pair<std::string, std::string>> const &options = {});
|
||||
|
||||
virtual ~PluginBase() = default;
|
||||
|
||||
const std::vector<Hook> &get_hooks() const override;
|
||||
bool has_tag(TagID id) const override;
|
||||
|
||||
// hooks that can be implemented by plugins
|
||||
void on_update(float delta_time) override{};
|
||||
void on_app_start(const std::string &app_id) override{};
|
||||
void on_app_close(const std::string &app_id) override{};
|
||||
void on_platform_close() override{};
|
||||
void on_post_draw(RenderContext &context) override{};
|
||||
void on_app_error(const std::string &app_id) override{};
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override{};
|
||||
|
||||
private:
|
||||
Tag<TAGS...> *tags = reinterpret_cast<Tag<TAGS...> *>(this);
|
||||
|
||||
std::vector<Hook> hooks;
|
||||
};
|
||||
|
||||
template <typename... TAGS>
|
||||
PluginBase<TAGS...>::PluginBase(const std::string name, const std::string description, const std::vector<Hook> &hooks, std::vector<std::pair<std::string, std::string>> const &commands, std::vector<std::pair<std::string, std::string>> const &options) :
|
||||
Plugin(name, description, commands, options), hooks{hooks}
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... TAGS>
|
||||
bool PluginBase<TAGS...>::has_tag(TagID id) const
|
||||
{
|
||||
return tags->has_tag(id);
|
||||
}
|
||||
|
||||
template <typename... TAGS>
|
||||
const std::vector<Hook> &PluginBase<TAGS...>::get_hooks() const
|
||||
{
|
||||
return hooks;
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,519 @@
|
||||
/* Copyright (c) 2019-2025, 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 "direct_window.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "core/instance.h"
|
||||
|
||||
#include "platform/headless_window.h"
|
||||
#include "platform/unix/direct_window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static KeyCode key_map[] = {
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Backspace,
|
||||
KeyCode::Tab,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Enter,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Escape,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Unknown,
|
||||
KeyCode::Space,
|
||||
KeyCode::_1,
|
||||
KeyCode::Apostrophe,
|
||||
KeyCode::Backslash,
|
||||
KeyCode::_4,
|
||||
KeyCode::_5,
|
||||
KeyCode::_7,
|
||||
KeyCode::Apostrophe,
|
||||
KeyCode::_9,
|
||||
KeyCode::_0,
|
||||
KeyCode::_8,
|
||||
KeyCode::Equal,
|
||||
KeyCode::Comma,
|
||||
KeyCode::Minus,
|
||||
KeyCode::Period,
|
||||
KeyCode::Slash,
|
||||
KeyCode::_0,
|
||||
KeyCode::_1,
|
||||
KeyCode::_2,
|
||||
KeyCode::_3,
|
||||
KeyCode::_4,
|
||||
KeyCode::_5,
|
||||
KeyCode::_6,
|
||||
KeyCode::_7,
|
||||
KeyCode::_8,
|
||||
KeyCode::_9,
|
||||
KeyCode::Semicolon,
|
||||
KeyCode::Semicolon,
|
||||
KeyCode::Comma,
|
||||
KeyCode::Equal,
|
||||
KeyCode::Period,
|
||||
KeyCode::Slash,
|
||||
KeyCode::_2,
|
||||
KeyCode::A,
|
||||
KeyCode::B,
|
||||
KeyCode::C,
|
||||
KeyCode::D,
|
||||
KeyCode::E,
|
||||
KeyCode::F,
|
||||
KeyCode::G,
|
||||
KeyCode::H,
|
||||
KeyCode::I,
|
||||
KeyCode::J,
|
||||
KeyCode::K,
|
||||
KeyCode::L,
|
||||
KeyCode::M,
|
||||
KeyCode::N,
|
||||
KeyCode::O,
|
||||
KeyCode::P,
|
||||
KeyCode::Q,
|
||||
KeyCode::R,
|
||||
KeyCode::S,
|
||||
KeyCode::T,
|
||||
KeyCode::U,
|
||||
KeyCode::V,
|
||||
KeyCode::W,
|
||||
KeyCode::X,
|
||||
KeyCode::Y,
|
||||
KeyCode::Z,
|
||||
KeyCode::LeftBracket,
|
||||
KeyCode::Backslash,
|
||||
KeyCode::RightBracket,
|
||||
KeyCode::_6,
|
||||
KeyCode::Minus,
|
||||
KeyCode::GraveAccent,
|
||||
KeyCode::A,
|
||||
KeyCode::B,
|
||||
KeyCode::C,
|
||||
KeyCode::D,
|
||||
KeyCode::E,
|
||||
KeyCode::F,
|
||||
KeyCode::G,
|
||||
KeyCode::H,
|
||||
KeyCode::I,
|
||||
KeyCode::J,
|
||||
KeyCode::K,
|
||||
KeyCode::L,
|
||||
KeyCode::M,
|
||||
KeyCode::N,
|
||||
KeyCode::O,
|
||||
KeyCode::P,
|
||||
KeyCode::Q,
|
||||
KeyCode::R,
|
||||
KeyCode::S,
|
||||
KeyCode::T,
|
||||
KeyCode::U,
|
||||
KeyCode::V,
|
||||
KeyCode::W,
|
||||
KeyCode::X,
|
||||
KeyCode::Y,
|
||||
KeyCode::Z,
|
||||
KeyCode::LeftBracket,
|
||||
KeyCode::Backslash,
|
||||
KeyCode::RightBracket,
|
||||
KeyCode::GraveAccent,
|
||||
KeyCode::Backspace};
|
||||
|
||||
static KeyCode map_multichar_key(int tty_fd, KeyCode initial)
|
||||
{
|
||||
static std::map<std::string, KeyCode> mc_map; // Static for one-time-init
|
||||
if (mc_map.size() == 0)
|
||||
{
|
||||
mc_map["[A"] = KeyCode::Up;
|
||||
mc_map["[B"] = KeyCode::Down;
|
||||
mc_map["[C"] = KeyCode::Right;
|
||||
mc_map["[D"] = KeyCode::Left;
|
||||
mc_map["[2~"] = KeyCode::Insert;
|
||||
mc_map["[3~"] = KeyCode::DelKey;
|
||||
mc_map["[5~"] = KeyCode::PageUp;
|
||||
mc_map["[6~"] = KeyCode::PageDown;
|
||||
mc_map["[H"] = KeyCode::Home;
|
||||
mc_map["[F"] = KeyCode::End;
|
||||
mc_map["OP"] = KeyCode::F1;
|
||||
mc_map["OQ"] = KeyCode::F2;
|
||||
mc_map["OR"] = KeyCode::F3;
|
||||
mc_map["OS"] = KeyCode::F4;
|
||||
mc_map["[15~"] = KeyCode::F5;
|
||||
mc_map["[17~"] = KeyCode::F6;
|
||||
mc_map["[18~"] = KeyCode::F7;
|
||||
mc_map["[19~"] = KeyCode::F8;
|
||||
mc_map["[20~"] = KeyCode::F9;
|
||||
mc_map["[21~"] = KeyCode::F10;
|
||||
mc_map["[23~"] = KeyCode::F11;
|
||||
mc_map["[24~"] = KeyCode::F12;
|
||||
}
|
||||
|
||||
char key;
|
||||
std::string buf;
|
||||
|
||||
while (::read(tty_fd, &key, 1) == 1)
|
||||
{
|
||||
buf += key;
|
||||
}
|
||||
|
||||
if (buf.size() == 0)
|
||||
{
|
||||
return initial; // Nothing new read, just return the initial char
|
||||
}
|
||||
|
||||
// Is it a code we recognise?
|
||||
auto iter = mc_map.find(buf);
|
||||
if (iter != mc_map.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
return KeyCode::Unknown;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
DirectWindow::DirectWindow(Platform *platform, const Window::Properties &properties) :
|
||||
Window(properties),
|
||||
platform{platform}
|
||||
{
|
||||
// Setup tty for reading keyboard from console
|
||||
if ((tty_fd = open("/dev/tty", O_RDWR | O_NDELAY)) > 0)
|
||||
{
|
||||
tcgetattr(tty_fd, &termio_prev);
|
||||
tcgetattr(tty_fd, &termio);
|
||||
cfmakeraw(&termio);
|
||||
termio.c_lflag |= ISIG;
|
||||
termio.c_oflag |= OPOST | ONLCR;
|
||||
termio.c_cc[VMIN] = 1;
|
||||
termio.c_cc[VTIME] = 0;
|
||||
|
||||
if (tcsetattr(tty_fd, TCSANOW, &termio) == -1)
|
||||
LOGW("Failed to set attribs for '/dev/tty'");
|
||||
}
|
||||
|
||||
platform->set_focus(true);
|
||||
}
|
||||
|
||||
DirectWindow::~DirectWindow()
|
||||
{
|
||||
if (tty_fd > 0)
|
||||
{
|
||||
tcsetattr(tty_fd, TCSANOW, &termio_prev);
|
||||
}
|
||||
}
|
||||
|
||||
VkSurfaceKHR DirectWindow::create_surface(vkb::core::InstanceC &instance)
|
||||
{
|
||||
return create_surface(instance.get_handle(), instance.get_first_gpu().get_handle());
|
||||
}
|
||||
|
||||
// Local structure for holding display candidate information
|
||||
struct Candidate
|
||||
{
|
||||
VkDisplayKHR display;
|
||||
VkDisplayPropertiesKHR display_props;
|
||||
VkDisplayModePropertiesKHR mode;
|
||||
VkDisplayPlaneCapabilitiesKHR caps;
|
||||
uint32_t plane_index;
|
||||
uint32_t stack_index;
|
||||
};
|
||||
|
||||
// Helper template to get a vector of properties using the Vulkan idiom of
|
||||
// querying size, then querying data
|
||||
template <typename T, typename F, typename... Targs>
|
||||
static std::vector<T> get_props(F func, Targs... args)
|
||||
{
|
||||
std::vector<T> result;
|
||||
uint32_t count;
|
||||
|
||||
VK_CHECK(func(args..., &count, nullptr));
|
||||
if (count > 0)
|
||||
{
|
||||
result.resize(count);
|
||||
VK_CHECK(func(args..., &count, result.data()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find all valid display candidates in the system
|
||||
static std::vector<Candidate> find_display_candidates(VkPhysicalDevice phys_dev, Window::Mode window_mode)
|
||||
{
|
||||
// Possible display config candidates
|
||||
std::vector<Candidate> candidates;
|
||||
|
||||
// Find all the displays connected to this platform
|
||||
auto display_properties = get_props<VkDisplayPropertiesKHR>(vkGetPhysicalDeviceDisplayPropertiesKHR, phys_dev);
|
||||
|
||||
// Find all the display planes
|
||||
auto plane_properties = get_props<VkDisplayPlanePropertiesKHR>(vkGetPhysicalDeviceDisplayPlanePropertiesKHR, phys_dev);
|
||||
|
||||
// Build a list of candidates
|
||||
for (uint32_t plane_index = 0; plane_index < plane_properties.size(); plane_index++)
|
||||
{
|
||||
const VkDisplayPlanePropertiesKHR &plane_props = plane_properties[plane_index];
|
||||
|
||||
// Find the list of displays compatible with the plane
|
||||
auto supported_displays = get_props<VkDisplayKHR>(vkGetDisplayPlaneSupportedDisplaysKHR, phys_dev, plane_index);
|
||||
|
||||
for (const auto &display : supported_displays)
|
||||
{
|
||||
auto props = std::find_if(display_properties.cbegin(), display_properties.cend(),
|
||||
[display](const VkDisplayPropertiesKHR &p) { return p.display == display; });
|
||||
if (props == display_properties.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot use if already on another display
|
||||
if (plane_props.currentDisplay != VK_NULL_HANDLE && plane_props.currentDisplay != display)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot use if identity transform is unsupported
|
||||
if ((props->supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find all the display modes for the display
|
||||
auto modes = get_props<VkDisplayModePropertiesKHR>(vkGetDisplayModePropertiesKHR, phys_dev, display);
|
||||
|
||||
for (auto mode : modes)
|
||||
{
|
||||
// Query capabilities of this mode/plane combination
|
||||
VkDisplayPlaneCapabilitiesKHR caps;
|
||||
VK_CHECK(vkGetDisplayPlaneCapabilitiesKHR(phys_dev, mode.displayMode, plane_index, &caps));
|
||||
|
||||
// Check for opaque alpha support
|
||||
if ((caps.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Eliminate modes that don't fit the plane capabilities
|
||||
if (mode.parameters.visibleRegion.width > caps.maxDstExtent.width ||
|
||||
mode.parameters.visibleRegion.height > caps.maxDstExtent.height ||
|
||||
mode.parameters.visibleRegion.width < caps.minDstExtent.width ||
|
||||
mode.parameters.visibleRegion.height < caps.minDstExtent.height)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (window_mode == Window::Mode::Fullscreen ||
|
||||
window_mode == Window::Mode::FullscreenBorderless)
|
||||
{
|
||||
// For full-screen modes (where the src image is the same size as the
|
||||
// display) we must also check the src extents are valid.
|
||||
if (mode.parameters.visibleRegion.width > caps.maxSrcExtent.width ||
|
||||
mode.parameters.visibleRegion.height > caps.maxSrcExtent.height ||
|
||||
mode.parameters.visibleRegion.width < caps.minSrcExtent.width ||
|
||||
mode.parameters.visibleRegion.height < caps.minSrcExtent.height)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to list of candidates
|
||||
candidates.push_back(Candidate{display, *props, mode, caps, plane_index,
|
||||
plane_properties[plane_index].currentStackIndex});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
VkSurfaceKHR DirectWindow::create_surface(VkInstance instance, VkPhysicalDevice phys_dev)
|
||||
{
|
||||
if (instance == VK_NULL_HANDLE || phys_dev == VK_NULL_HANDLE)
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Find possible display config candidates
|
||||
std::vector<Candidate> candidates = find_display_candidates(phys_dev, properties.mode);
|
||||
if (candidates.empty())
|
||||
{
|
||||
LOGE("Direct-to-display: No compatible display candidates found");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// 'candidates' contains valid potential display configs. Find the best one.
|
||||
// Look for the candidate with the closest resolution to our requested width & height.
|
||||
uint32_t best_candidate = 0;
|
||||
uint32_t wanted_area = properties.extent.width * properties.extent.height;
|
||||
uint32_t best_diff = ~0;
|
||||
|
||||
for (size_t c = 0; c < candidates.size(); c++)
|
||||
{
|
||||
const Candidate &cand = candidates[c];
|
||||
|
||||
uint32_t area = cand.display_props.physicalResolution.width *
|
||||
cand.display_props.physicalResolution.height;
|
||||
|
||||
uint32_t diff = abs(static_cast<int32_t>(area) - static_cast<int32_t>(wanted_area));
|
||||
|
||||
if (diff < best_diff)
|
||||
{
|
||||
best_diff = diff;
|
||||
best_candidate = c;
|
||||
}
|
||||
}
|
||||
|
||||
const Candidate &best = candidates[best_candidate];
|
||||
|
||||
// Get the full display mode extent
|
||||
full_extent.width = best.mode.parameters.visibleRegion.width;
|
||||
full_extent.height = best.mode.parameters.visibleRegion.height;
|
||||
|
||||
VkExtent2D image_extent;
|
||||
if (properties.mode == Window::Mode::Fullscreen ||
|
||||
properties.mode == Window::Mode::FullscreenBorderless)
|
||||
{
|
||||
// Fullscreen & Borderless options create a surface that matches the display size
|
||||
image_extent.width = full_extent.width;
|
||||
image_extent.height = full_extent.height;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal and stretch options create a surface at the requested width & height
|
||||
// clamped to the plane capabilities in play
|
||||
image_extent.width = std::min(best.caps.maxSrcExtent.width,
|
||||
std::max(properties.extent.width, best.caps.minSrcExtent.width));
|
||||
image_extent.height = std::min(best.caps.maxSrcExtent.height,
|
||||
std::max(properties.extent.height, best.caps.minSrcExtent.height));
|
||||
}
|
||||
|
||||
// Calculate the display DPI
|
||||
constexpr float mm_per_inch = 25.4f;
|
||||
|
||||
dpi = mm_per_inch * best.display_props.physicalResolution.width / best.display_props.physicalDimensions.width;
|
||||
|
||||
// Create the surface
|
||||
VkDisplaySurfaceCreateInfoKHR surface_create_info{};
|
||||
surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
|
||||
surface_create_info.displayMode = best.mode.displayMode;
|
||||
surface_create_info.planeIndex = best.plane_index;
|
||||
surface_create_info.planeStackIndex = best.stack_index;
|
||||
surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
surface_create_info.alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
|
||||
surface_create_info.imageExtent = image_extent;
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VK_CHECK(vkCreateDisplayPlaneSurfaceKHR(instance, &surface_create_info, nullptr, &surface));
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
void DirectWindow::process_events()
|
||||
{
|
||||
if (tty_fd > 0)
|
||||
{
|
||||
if (key_down != KeyCode::Unknown)
|
||||
{
|
||||
// Signal release for the key we previously reported as down
|
||||
// (we don't get separate press & release from the terminal)
|
||||
platform->input_event(KeyInputEvent{key_down, KeyAction::Up});
|
||||
key_down = KeyCode::Unknown;
|
||||
}
|
||||
|
||||
// See if there is a new keypress
|
||||
uint8_t key = 0;
|
||||
int bytes = ::read(tty_fd, &key, 1);
|
||||
|
||||
if (bytes > 0 && key > 0 && (key < sizeof(key_map) / sizeof(KeyCode)))
|
||||
{
|
||||
// Map the key
|
||||
key_down = key_map[key];
|
||||
|
||||
// Is this potentially a multi-character code?
|
||||
if (key_down == KeyCode::Escape)
|
||||
{
|
||||
key_down = map_multichar_key(tty_fd, key_down);
|
||||
}
|
||||
|
||||
// Signal the press
|
||||
platform->input_event(KeyInputEvent{key_down, KeyAction::Down});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectWindow::should_close()
|
||||
{
|
||||
return !keep_running;
|
||||
}
|
||||
|
||||
void DirectWindow::close()
|
||||
{
|
||||
keep_running = false;
|
||||
}
|
||||
|
||||
float DirectWindow::get_dpi_factor() const
|
||||
{
|
||||
const float win_base_density = 96.0f;
|
||||
return dpi / win_base_density;
|
||||
}
|
||||
|
||||
bool DirectWindow::get_display_present_info(VkDisplayPresentInfoKHR *info,
|
||||
uint32_t src_width, uint32_t src_height) const
|
||||
{
|
||||
// Only stretch mode needs to supply a VkDisplayPresentInfoKHR
|
||||
if (properties.mode != Window::Mode::FullscreenStretch || !info)
|
||||
return false;
|
||||
|
||||
info->sType = VK_STRUCTURE_TYPE_DISPLAY_PRESENT_INFO_KHR;
|
||||
info->pNext = nullptr;
|
||||
info->srcRect = {0, 0, src_width, src_height};
|
||||
info->dstRect = {0, 0, full_extent.width, full_extent.height};
|
||||
info->persistent = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<const char *> DirectWindow::get_required_surface_extensions() const
|
||||
{
|
||||
return {VK_KHR_DISPLAY_EXTENSION_NAME};
|
||||
}
|
||||
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Copyright (c) 2019-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "platform/platform.h"
|
||||
#include "platform/window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
/**
|
||||
* @brief Direct2Display window
|
||||
*/
|
||||
class DirectWindow : public Window
|
||||
{
|
||||
public:
|
||||
DirectWindow(Platform *platform, const Window::Properties &properties);
|
||||
|
||||
virtual ~DirectWindow();
|
||||
|
||||
virtual VkSurfaceKHR create_surface(vkb::core::InstanceC &instance) override;
|
||||
|
||||
virtual VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice physical_device) override;
|
||||
|
||||
virtual bool should_close() override;
|
||||
|
||||
virtual void process_events() override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
virtual bool get_display_present_info(VkDisplayPresentInfoKHR *info,
|
||||
uint32_t src_width, uint32_t src_height) const override;
|
||||
|
||||
float get_dpi_factor() const override;
|
||||
|
||||
std::vector<const char *> get_required_surface_extensions() const override;
|
||||
|
||||
private:
|
||||
void poll_terminal();
|
||||
|
||||
private:
|
||||
mutable bool keep_running = true;
|
||||
Platform *platform = nullptr;
|
||||
float dpi;
|
||||
int tty_fd;
|
||||
struct termios termio;
|
||||
struct termios termio_prev;
|
||||
KeyCode key_down = KeyCode::Unknown;
|
||||
Extent full_extent{};
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Copyright (c) 2019-2024, 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 "unix_d2d_platform.h"
|
||||
|
||||
#include "platform/headless_window.h"
|
||||
#include "platform/unix/direct_window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
UnixD2DPlatform::UnixD2DPlatform(const PlatformContext &context) :
|
||||
Platform{context}
|
||||
{
|
||||
}
|
||||
|
||||
void UnixD2DPlatform::create_window(const Window::Properties &properties)
|
||||
{
|
||||
if (properties.mode == vkb::Window::Mode::Headless)
|
||||
{
|
||||
window = std::make_unique<HeadlessWindow>(properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
window = std::make_unique<DirectWindow>(this, properties);
|
||||
}
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,34 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class UnixD2DPlatform : public Platform
|
||||
{
|
||||
public:
|
||||
UnixD2DPlatform(const PlatformContext &context);
|
||||
|
||||
virtual ~UnixD2DPlatform() = default;
|
||||
|
||||
protected:
|
||||
virtual void create_window(const Window::Properties &properties) override;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (c) 2019-2024, 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 "unix_platform.h"
|
||||
|
||||
#include "platform/glfw_window.h"
|
||||
#include "platform/headless_window.h"
|
||||
|
||||
#ifndef VK_MVK_MACOS_SURFACE_EXTENSION_NAME
|
||||
# define VK_MVK_MACOS_SURFACE_EXTENSION_NAME "VK_MVK_macos_surface"
|
||||
#endif
|
||||
|
||||
#ifndef VK_KHR_XCB_SURFACE_EXTENSION_NAME
|
||||
# define VK_KHR_XCB_SURFACE_EXTENSION_NAME "VK_KHR_xcb_surface"
|
||||
#endif
|
||||
|
||||
#ifndef VK_EXT_METAL_SURFACE_EXTENSION_NAME
|
||||
# define VK_EXT_METAL_SURFACE_EXTENSION_NAME "VK_EXT_metal_surface"
|
||||
#endif
|
||||
|
||||
#ifndef VK_KHR_XLIB_SURFACE_EXTENSION_NAME
|
||||
# define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface"
|
||||
#endif
|
||||
|
||||
#ifndef VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME
|
||||
# define VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME "VK_KHR_wayland_surface"
|
||||
#endif
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
|
||||
UnixPlatform::UnixPlatform(const PlatformContext &context, const UnixType &type) :
|
||||
Platform{context}, type{type}
|
||||
{
|
||||
}
|
||||
|
||||
void UnixPlatform::create_window(const Window::Properties &properties)
|
||||
{
|
||||
if (properties.mode == vkb::Window::Mode::Headless)
|
||||
{
|
||||
window = std::make_unique<HeadlessWindow>(properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
window = std::make_unique<GlfwWindow>(this, properties);
|
||||
}
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Copyright (c) 2018-2024, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
enum UnixType
|
||||
{
|
||||
Mac,
|
||||
Ios,
|
||||
Linux
|
||||
};
|
||||
|
||||
class UnixPlatform : public Platform
|
||||
{
|
||||
public:
|
||||
UnixPlatform(const PlatformContext &context, const UnixType &type);
|
||||
|
||||
virtual ~UnixPlatform() = default;
|
||||
|
||||
protected:
|
||||
virtual void create_window(const Window::Properties &properties) override;
|
||||
|
||||
private:
|
||||
UnixType type;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Copyright (c) 2018-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 "window.h"
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
Window::Window(const Properties &properties) :
|
||||
properties{properties}
|
||||
{
|
||||
}
|
||||
|
||||
void Window::process_events()
|
||||
{
|
||||
}
|
||||
|
||||
Window::Extent Window::resize(const Extent &new_extent)
|
||||
{
|
||||
if (properties.resizable)
|
||||
{
|
||||
properties.extent.width = new_extent.width;
|
||||
properties.extent.height = new_extent.height;
|
||||
}
|
||||
|
||||
return properties.extent;
|
||||
}
|
||||
|
||||
const Window::Extent &Window::get_extent() const
|
||||
{
|
||||
return properties.extent;
|
||||
}
|
||||
|
||||
float Window::get_content_scale_factor() const
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
Window::Mode Window::get_window_mode() const
|
||||
{
|
||||
return properties.mode;
|
||||
}
|
||||
|
||||
bool Window::get_display_present_info(VkDisplayPresentInfoKHR *info,
|
||||
uint32_t src_width, uint32_t src_height) const
|
||||
{
|
||||
// Default is to not use the extra present info
|
||||
return false;
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,160 @@
|
||||
/* Copyright (c) 2018-2025, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/optional.h"
|
||||
#include "common/vk_common.h"
|
||||
#include "core/instance.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
/**
|
||||
* @brief An interface class, declaring the behaviour of a Window
|
||||
*/
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
struct Extent
|
||||
{
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct OptionalExtent
|
||||
{
|
||||
Optional<uint32_t> width;
|
||||
Optional<uint32_t> height;
|
||||
};
|
||||
|
||||
enum class Mode
|
||||
{
|
||||
Headless,
|
||||
Fullscreen,
|
||||
FullscreenBorderless,
|
||||
FullscreenStretch,
|
||||
Default
|
||||
};
|
||||
|
||||
enum class Vsync
|
||||
{
|
||||
OFF,
|
||||
ON,
|
||||
Default
|
||||
};
|
||||
|
||||
struct OptionalProperties
|
||||
{
|
||||
Optional<std::string> title;
|
||||
Optional<Mode> mode;
|
||||
Optional<bool> resizable;
|
||||
Optional<Vsync> vsync;
|
||||
OptionalExtent extent;
|
||||
};
|
||||
|
||||
struct Properties
|
||||
{
|
||||
std::string title = "";
|
||||
Mode mode = Mode::Default;
|
||||
bool resizable = true;
|
||||
Vsync vsync = Vsync::Default;
|
||||
Extent extent = {1280, 720};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Constructs a Window
|
||||
* @param properties The preferred configuration of the window
|
||||
*/
|
||||
Window(const Properties &properties);
|
||||
|
||||
virtual ~Window() = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a handle from the platform's Vulkan surface
|
||||
* @param instance A Vulkan instance
|
||||
* @returns A VkSurfaceKHR handle, for use by the application
|
||||
*/
|
||||
virtual VkSurfaceKHR create_surface(vkb::core::InstanceC &instance) = 0;
|
||||
|
||||
/**
|
||||
* @brief Gets a handle from the platform's Vulkan surface
|
||||
* @param instance A Vulkan instance
|
||||
* @param physical_device A Vulkan PhysicalDevice
|
||||
* @returns A VkSurfaceKHR handle, for use by the application
|
||||
*/
|
||||
virtual VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice physical_device) = 0;
|
||||
|
||||
/**
|
||||
* @brief Checks if the window should be closed
|
||||
*/
|
||||
virtual bool should_close() = 0;
|
||||
|
||||
/**
|
||||
* @brief Handles the processing of all underlying window events
|
||||
*/
|
||||
virtual void process_events();
|
||||
|
||||
/**
|
||||
* @brief Requests to close the window
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* @return The dot-per-inch scale factor
|
||||
*/
|
||||
virtual float get_dpi_factor() const = 0;
|
||||
|
||||
/**
|
||||
* @return The scale factor for systems with heterogeneous window and pixel coordinates
|
||||
*/
|
||||
virtual float get_content_scale_factor() const;
|
||||
|
||||
/**
|
||||
* @brief Attempt to resize the window - not guaranteed to change
|
||||
*
|
||||
* @param extent The preferred window extent
|
||||
* @return Extent The new window extent
|
||||
*/
|
||||
Extent resize(const Extent &extent);
|
||||
|
||||
/**
|
||||
* @brief Get the display present info for the window if needed
|
||||
*
|
||||
* @param info Filled in when the method returns true
|
||||
* @param src_width The width of the surface being presented
|
||||
* @param src_height The height of the surface being presented
|
||||
* @return true if the present info was filled in and should be used
|
||||
* @return false if the extra present info should not be used. info is left untouched.
|
||||
*/
|
||||
virtual bool get_display_present_info(VkDisplayPresentInfoKHR *info,
|
||||
uint32_t src_width, uint32_t src_height) const;
|
||||
|
||||
virtual std::vector<const char *> get_required_surface_extensions() const = 0;
|
||||
|
||||
const Extent &get_extent() const;
|
||||
|
||||
Mode get_window_mode() const;
|
||||
|
||||
inline const Properties &get_properties() const
|
||||
{
|
||||
return properties;
|
||||
}
|
||||
|
||||
protected:
|
||||
Properties properties;
|
||||
};
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,46 @@
|
||||
/* Copyright (c) 2019-2024, 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 "windows_platform.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <iostream>
|
||||
#include <shellapi.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "platform/glfw_window.h"
|
||||
#include "platform/headless_window.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
WindowsPlatform::WindowsPlatform(const PlatformContext &context) :
|
||||
Platform(context)
|
||||
{
|
||||
}
|
||||
|
||||
void WindowsPlatform::create_window(const Window::Properties &properties)
|
||||
{
|
||||
if (properties.mode == vkb::Window::Mode::Headless)
|
||||
{
|
||||
window = std::make_unique<HeadlessWindow>(properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
window = std::make_unique<GlfwWindow>(this, properties);
|
||||
}
|
||||
}
|
||||
} // namespace vkb
|
||||
@@ -0,0 +1,34 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
namespace vkb
|
||||
{
|
||||
class WindowsPlatform : public Platform
|
||||
{
|
||||
public:
|
||||
WindowsPlatform(const PlatformContext &context);
|
||||
|
||||
virtual ~WindowsPlatform() = default;
|
||||
|
||||
protected:
|
||||
virtual void create_window(const Window::Properties &properties) override;
|
||||
};
|
||||
} // namespace vkb
|
||||
Reference in New Issue
Block a user