/* * This source file is part of RmlUi, the HTML/CSS Interface Middleware * * For the latest information, see http://github.com/mikke89/RmlUi * * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd * Copyright (c) 2019-2023 The RmlUi Team, and contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "RmlUi_Backend.h" #include "RmlUi_Renderer_VK.h" // This space is intentional to prevent autoformat from reordering RmlUi_Renderer_VK behind RmlUi_Platform_GLFW #include "RmlUi_Platform_GLFW.h" #include #include #include #include #include #include #include #include static void SetupCallbacks(GLFWwindow* window); static void LogErrorFromGLFW(int error, const char* description) { Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description); } /** Global data used by this backend. Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). */ struct BackendData { SystemInterface_GLFW system_interface; RenderInterface_VK render_interface; GLFWwindow* window = nullptr; Rml::Context* context = nullptr; KeyDownCallback key_down_callback = nullptr; int glfw_active_modifiers = 0; bool running = true; bool context_dimensions_dirty = true; }; static Rml::UniquePtr data; bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize) { RMLUI_ASSERT(!data); glfwSetErrorCallback(LogErrorFromGLFW); if (!glfwInit()) { glfwTerminate(); return false; } glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE); glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); GLFWwindow* window = glfwCreateWindow(width, height, window_name, nullptr, nullptr); if (!window) { Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW failed to create window"); return false; } data = Rml::MakeUnique(); data->window = window; uint32_t count; const char** extensions = glfwGetRequiredInstanceExtensions(&count); RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query GLFW Vulkan extensions"); if (!data->render_interface.Initialize(Rml::Vector(extensions, extensions + count), [](VkInstance instance, VkSurfaceKHR* out_surface) { return glfwCreateWindowSurface(instance, data->window, nullptr, out_surface) == VkResult::VK_SUCCESS; ; })) { data.reset(); Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface"); return false; } data->system_interface.SetWindow(window); data->render_interface.SetViewport(width, height); // Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields. glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE); SetupCallbacks(window); return true; } void Backend::Shutdown() { RMLUI_ASSERT(data); data->render_interface.Shutdown(); glfwDestroyWindow(data->window); data.reset(); glfwTerminate(); } Rml::SystemInterface* Backend::GetSystemInterface() { RMLUI_ASSERT(data); return &data->system_interface; } Rml::RenderInterface* Backend::GetRenderInterface() { RMLUI_ASSERT(data); return &data->render_interface; } static bool WaitForValidSwapchain() { bool result = true; // In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any render // calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead, we keep the // application inside this loop until we are able to recreate the swapchain and render again. while (!data->render_interface.IsSwapchainValid()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (glfwWindowShouldClose(data->window)) { glfwRestoreWindow(data->window); result = false; } glfwPollEvents(); data->render_interface.RecreateSwapchain(); } return result; } bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save) { RMLUI_ASSERT(data && context); bool result = data->running; if (data->context_dimensions_dirty) { data->context_dimensions_dirty = false; Rml::Vector2i window_size; float dp_ratio = 1.f; glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y); glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); context->SetDimensions(window_size); context->SetDensityIndependentPixelRatio(dp_ratio); } data->context = context; data->key_down_callback = key_down_callback; if (power_save) glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)); else glfwPollEvents(); if (!WaitForValidSwapchain()) result = false; data->context = nullptr; data->key_down_callback = nullptr; result = !glfwWindowShouldClose(data->window); glfwSetWindowShouldClose(data->window, GLFW_FALSE); return result; } void Backend::RequestExit() { RMLUI_ASSERT(data); data->running = false; glfwSetWindowShouldClose(data->window, GLFW_TRUE); } void Backend::BeginFrame() { RMLUI_ASSERT(data); data->render_interface.BeginFrame(); } void Backend::PresentFrame() { RMLUI_ASSERT(data); data->render_interface.EndFrame(); } static void SetupCallbacks(GLFWwindow* window) { RMLUI_ASSERT(data); // Key input glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) { if (!data->context) return; // Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events. data->glfw_active_modifiers = glfw_mods; // Override the default key event callback to add global shortcuts for the samples. Rml::Context* context = data->context; KeyDownCallback key_down_callback = data->key_down_callback; switch (glfw_action) { case GLFW_PRESS: case GLFW_REPEAT: { const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key); const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods); float dp_ratio = 1.f; glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); // See if we have any global shortcuts that take priority over the context. if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true)) break; // Otherwise, hand the event over to the context by calling the input handler as normal. if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods)) break; // The key was not consumed by the context either, try keyboard shortcuts of lower priority. if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false)) break; } break; case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break; } }); glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); }); glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); }); // Mouse input glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) { RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers); }); glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) { data->glfw_active_modifiers = mods; RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods); }); glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) { RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers); }); // Window events glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) { data->render_interface.SetViewport(width, height); RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height); }); glfwSetWindowContentScaleCallback(window, [](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); }); }