This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
@@ -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
+87
View File
@@ -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
+169
View File
@@ -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
+106
View File
@@ -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
+143
View File
@@ -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
+415
View File
@@ -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
+58
View File
@@ -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
+67
View File
@@ -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
+59
View File
@@ -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
+112
View File
@@ -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
+254
View File
@@ -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
+38
View File
@@ -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
+90
View File
@@ -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
+81
View File
@@ -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
+88
View File
@@ -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
+571
View File
@@ -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
+202
View File
@@ -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
+69
View File
@@ -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
+310
View File
@@ -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
+76
View File
@@ -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
+519
View File
@@ -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
+70
View File
@@ -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
+62
View File
@@ -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
+44
View File
@@ -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
+65
View File
@@ -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
+160
View File
@@ -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