From 84f7647258c747fd691ec76604cd21cd93200e5c Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Sun, 1 Oct 2023 11:38:27 +0100 Subject: [PATCH] Add imgui --- CMakeLists.txt | 4 + include/gfx_device.h | 5 + include/scene.h | 10 +- src/application.cpp | 98 +- src/gfx_device_vulkan.cpp | 81 +- src/imgui/imconfig.h | 1 + src/imgui/imgui_impl_sdl2.cpp | 659 ++++++++++++++ src/imgui/imgui_impl_sdl2.h | 43 + src/imgui/imgui_impl_vulkan.cpp | 1474 +++++++++++++++++++++++++++++++ src/imgui/imgui_impl_vulkan.h | 148 ++++ src/renderer.cpp | 8 +- src/scene.cpp | 7 +- src/window.cpp | 107 ++- test/res/models/AxesTexture.png | Bin 0 -> 40120 bytes test/res/models/MY_AXES.dae | 4 +- 15 files changed, 2585 insertions(+), 64 deletions(-) create mode 100644 src/imgui/imgui_impl_sdl2.cpp create mode 100644 src/imgui/imgui_impl_sdl2.h create mode 100644 src/imgui/imgui_impl_vulkan.cpp create mode 100644 src/imgui/imgui_impl_vulkan.h create mode 100644 test/res/models/AxesTexture.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ba30be..bd4ea6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,10 @@ set(SRC_FILES "src/imgui/imgui.h" "src/imgui/imgui_demo.cpp" "src/imgui/imgui_draw.cpp" + "src/imgui/imgui_impl_sdl2.cpp" + "src/imgui/imgui_impl_sdl2.h" + "src/imgui/imgui_impl_vulkan.cpp" + "src/imgui/imgui_impl_vulkan.h" "src/imgui/imgui_internal.h" "src/imgui/imgui_tables.cpp" "src/imgui/imgui_widgets.cpp" diff --git a/include/gfx_device.h b/include/gfx_device.h index cd36de6..95f8670 100644 --- a/include/gfx_device.h +++ b/include/gfx_device.h @@ -6,6 +6,7 @@ #include "gfx.h" struct SDL_Window; // +struct ImDrawData; // "imgui/imgui.h" namespace engine { @@ -20,6 +21,10 @@ class GFXDevice { void GetViewportSize(uint32_t* w, uint32_t* h); + void SetupImguiBackend(); + void ShutdownImguiBackend(); + void CmdRenderImguiDrawData(gfx::DrawBuffer* draw_buffer, ImDrawData* draw_data); + gfx::DrawBuffer* BeginRender(); /* - draw_buffer MUST be a valid pointer returned by BeginRender(). diff --git a/include/scene.h b/include/scene.h index 8ba6852..086fc59 100644 --- a/include/scene.h +++ b/include/scene.h @@ -8,6 +8,7 @@ #include #include +#include #include "ecs.h" #include "event_system.h" @@ -32,7 +33,9 @@ class Scene { /* ecs stuff */ Entity CreateEntity(const std::string& tag, Entity parent = 0, - const glm::vec3& pos = glm::vec3{0.0f, 0.0f, 0.0f}); + const glm::vec3& pos = glm::vec3{0.0f, 0.0f, 0.0f}, + const glm::quat& rot = glm::quat{1.0f, 0.0f, 0.0f, 0.0f}, + const glm::vec3& scl = glm::vec3{1.0f, 1.0f, 1.0f}); Entity GetEntity(const std::string& tag, Entity parent = 0); @@ -107,9 +110,10 @@ class Scene { private: Application* const app_; - public: + + public: Entity next_entity_id_ = 1; // 0 is not a valid entity -private: + private: uint64_t framecount_ = 0; /* ecs stuff */ diff --git a/src/application.cpp b/src/application.cpp index d9213d4..ed2cc91 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,13 +1,19 @@ #include "application.h" +#include #include #include #include #include #include +#include #include +#include "imgui/imgui.h" +#include "imgui/imgui_impl_sdl2.h" +#include "imgui/imgui_impl_vulkan.h" + #include "gfx.h" #include "gfx_device.h" #include "input_manager.h" @@ -29,6 +35,10 @@ #define WIN_MAX_PATH 260 #endif +static struct ImGuiThings { + ImGuiContext* context; +} im_gui_things; + namespace engine { static std::filesystem::path getResourcesPath() { @@ -78,6 +88,10 @@ Application::Application(const char* appName, const char* appVersion, RegisterResourceManager(); RegisterResourceManager(); + im_gui_things.context = ImGui::CreateContext(); + // ImGuiIO& io = ImGui::GetIO() + ImGui_ImplSDL2_InitForVulkan(window_->GetHandle()); + renderer_ = std::make_unique( appName, appVersion, window_->GetHandle(), graphicsSettings); @@ -123,7 +137,7 @@ Application::Application(const char* appName, const char* appVersion, GetResourceManager()->AddPersistent( "builtin.skybox", std::move(skyboxShader)); } - { + if (0) { resources::Shader::VertexParams vertParams{}; vertParams.has_normal = true; vertParams.has_uv0 = true; @@ -150,7 +164,11 @@ Application::Application(const char* appName, const char* appVersion, } } -Application::~Application() {} +Application::~Application() { + renderer_->GetDevice()->ShutdownImguiBackend(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(im_gui_things.context); +} void Application::GameLoop() { LOG_DEBUG("Begin game loop..."); @@ -162,6 +180,12 @@ void Application::GameLoop() { auto endFrame = beginFrame + FRAMETIME_LIMIT; auto lastTick = window_->GetNanos(); + std::array delta_times{}; + + struct DebugMenuState { + bool menu_active = false; + bool show_info_window = true; + } debug_menu_state; // single-threaded game loop while (window_->IsRunning()) { @@ -176,16 +200,69 @@ void Application::GameLoop() { window_->ResetAvgFPS(); } - /* render */ - renderer_->PreRender(window()->GetWindowResized(), scene->GetComponent(scene->GetEntity("camera"))->world_matrix); - - if (scene) { - auto mesh_render_system = scene->GetSystem(); - const RenderList* static_list = mesh_render_system->GetStaticRenderList(); - const RenderList* dynamic_list = mesh_render_system->GetDynamicRenderList(); - renderer_->Render(*static_list, *dynamic_list); + if (window_->GetKeyPress(inputs::Key::K_F5)) { + bool show_window = window_->MouseCaptured(); + debug_menu_state.menu_active = show_window; + window_->SetRelativeMouseMode(!show_window); } + if (window_->GetKeyPress(inputs::Key::K_F6)) { + debug_menu_state.show_info_window = !debug_menu_state.show_info_window; + } + + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + if (debug_menu_state.menu_active) { + if (ImGui::Begin("debugMenu")) { + ImGui::Text("Test!"); + } + ImGui::End(); + } + + if (debug_menu_state.show_info_window) { + if (ImGui::Begin("infoWindow", nullptr, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::Text("Scene hierarchy:"); + + std::function find_depth = [&](Entity e, int current_depth)-> int { + Entity parent = scene->GetComponent(e)->parent; + if (parent == 0) return current_depth; + else { + return find_depth(parent, current_depth + 1); + } + }; + { + for (Entity i = 1; i < scene->next_entity_id_; ++i) { + auto t = scene->GetComponent(i); + std::string tabs{}; + int depth = find_depth(i, 0); + for (int j = 0; j < depth; ++j) tabs += std::string{ " " }; + ImGui::Text("%s%s", tabs, t->tag.c_str()); + + } + } + + } + ImGui::End(); + } + + ImGui::Render(); + + const RenderList* static_list = nullptr; + const RenderList* dynamic_list = nullptr; + glm::mat4 camera_transform{1.0f}; + if (scene) { + camera_transform = + scene->GetComponent(scene->GetEntity("camera")) + ->world_matrix; + auto mesh_render_system = scene->GetSystem(); + static_list = mesh_render_system->GetStaticRenderList(); + dynamic_list = mesh_render_system->GetDynamicRenderList(); + } + renderer_->PreRender(window()->GetWindowResized(), camera_transform); + renderer_->Render(*static_list, *dynamic_list); + /* poll events */ window_->GetInputAndEvents(); @@ -195,6 +272,7 @@ void Application::GameLoop() { } beginFrame = endFrame; endFrame = beginFrame + FRAMETIME_LIMIT; + delta_times[window_->GetFrameCount() % delta_times.size()] = window_->dt(); } renderer_->GetDevice()->WaitIdle(); diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index b4a98d5..1ff6cb3 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -37,6 +37,9 @@ #include +#include "imgui/imgui.h" +#include "imgui/imgui_impl_vulkan.h" + #include "gfx_device.h" #include "vulkan/instance.h" #include "vulkan/device.h" @@ -50,10 +53,14 @@ static constexpr bool flip_viewport = false; inline static void checkVulkanError(VkResult errorCode, int lineNo) { - if (errorCode != VK_SUCCESS) { - const std::string message("VULKAN ERROR ON LINE " + std::to_string(lineNo)); - throw std::runtime_error(message.c_str()); - } + if (errorCode != VK_SUCCESS) { + const std::string message("VULKAN ERROR ON LINE " + std::to_string(lineNo)); + throw std::runtime_error(message.c_str()); + } +} + +static void check_vk_result(VkResult code) { + checkVulkanError(code, -1); } #undef VKCHECK @@ -586,6 +593,72 @@ void GFXDevice::GetViewportSize(uint32_t* w, uint32_t* h) { } } +void GFXDevice::SetupImguiBackend() +{ + ImGui_ImplVulkan_InitInfo initInfo{}; + initInfo.Instance = pimpl->instance.instance; + initInfo.PhysicalDevice = pimpl->device.physicalDevice; + initInfo.Device = pimpl->device.device; + initInfo.QueueFamily = pimpl->device.queues.presentAndDrawQueueFamily; + initInfo.Queue = pimpl->device.queues.drawQueues.back(); // hopefully this isn't used by anything else? + initInfo.PipelineCache = VK_NULL_HANDLE; + initInfo.DescriptorPool = pimpl->descriptorPool; + initInfo.Subpass = 0; + initInfo.MinImageCount = 3; + initInfo.ImageCount = 3; + initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + initInfo.Allocator = nullptr; + initInfo.CheckVkResultFn = check_vk_result; + bool success = ImGui_ImplVulkan_Init(&initInfo, pimpl->swapchain.renderpass); + if (!success) throw std::runtime_error("ImGui_ImplVulkan_Init failed!"); + + /* begin command buffer */ + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = pimpl->graphicsCommandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &allocInfo, + &commandBuffer)); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VKCHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + ImGui_ImplVulkan_CreateFontsTexture(commandBuffer); + + VKCHECK(vkEndCommandBuffer(commandBuffer)); + + // submit + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VKCHECK(vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, + VK_NULL_HANDLE)); + + VKCHECK(vkQueueWaitIdle(pimpl->device.queues.drawQueues[0])); + + vkFreeCommandBuffers(pimpl->device.device, pimpl->graphicsCommandPool, 1, + &commandBuffer); + + ImGui_ImplVulkan_DestroyFontUploadObjects(); +} + +void GFXDevice::ShutdownImguiBackend() +{ + ImGui_ImplVulkan_Shutdown(); +} + +void GFXDevice::CmdRenderImguiDrawData(gfx::DrawBuffer* draw_buffer, ImDrawData* draw_data) +{ + ImGui_ImplVulkan_RenderDrawData(draw_data, draw_buffer->frameData.drawBuf); +} + gfx::DrawBuffer* GFXDevice::BeginRender() { VkResult res; diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index b56ba49..e0545ca 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -127,3 +127,4 @@ namespace ImGui void MyFunction(const char* name, MyMatrix44* mtx); } */ +//#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES \ No newline at end of file diff --git a/src/imgui/imgui_impl_sdl2.cpp b/src/imgui/imgui_impl_sdl2.cpp new file mode 100644 index 0000000..87efd51 --- /dev/null +++ b/src/imgui/imgui_impl_sdl2.cpp @@ -0,0 +1,659 @@ +// dear imgui: Platform Backend for SDL2 +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) +// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) +// (Prefer SDL 2.0.5+ for full feature support.) + +// Implemented features: +// [X] Platform: Clipboard support. +// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen. +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306) +// 2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702) +// 2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644) +// 2023-02-07: Implement IME handler (io.SetPlatformImeDataFn will call SDL_SetTextInputRect()/SDL_StartTextInput()). +// 2023-02-07: *BREAKING CHANGE* Renamed this backend file from imgui_impl_sdl.cpp/.h to imgui_impl_sdl2.cpp/.h in prevision for the future release of SDL3. +// 2023-02-02: Avoid calling SDL_SetCursor() when cursor has not changed, as the function is surprisingly costly on Mac with latest SDL (may be fixed in next SDL version). +// 2023-02-02: Added support for SDL 2.0.18+ preciseX/preciseY mouse wheel data for smooth scrolling + Scaling X value on Emscripten (bug?). (#4019, #6096) +// 2023-02-02: Removed SDL_MOUSEWHEEL value clamping, as values seem correct in latest Emscripten. (#4019) +// 2023-02-01: Flipping SDL_MOUSEWHEEL 'wheel.x' value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463) +// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. +// 2022-09-26: Inputs: Disable SDL 2.0.22 new "auto capture" (SDL_HINT_MOUSE_AUTO_CAPTURE) which prevents drag and drop across windows for multi-viewport support + don't capture when drag and dropping. (#5710) +// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). +// 2022-03-22: Inputs: Fix mouse position issues when dragging outside of boundaries. SDL_CaptureMouse() erroneously still gives out LEAVE events when hovering OS decorations. +// 2022-03-22: Inputs: Added support for extra mouse buttons (SDL_BUTTON_X1/SDL_BUTTON_X2). +// 2022-02-04: Added SDL_Renderer* parameter to ImGui_ImplSDL2_InitForSDLRenderer(), so we can use SDL_GetRendererOutputSize() instead of SDL_GL_GetDrawableSize() when bound to a SDL_Renderer. +// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. +// 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[]. +// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+). +// 2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates. +// 2022-01-12: Update mouse inputs using SDL_MOUSEMOTION/SDL_WINDOWEVENT_LEAVE + fallback to provide it when focused but not hovered/captured. More standard and will allow us to pass it to future input queue API. +// 2022-01-12: Maintain our own copy of MouseButtonsDown mask instead of using ImGui::IsAnyMouseDown() which will be obsoleted. +// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range. +// 2021-08-17: Calling io.AddFocusEvent() on SDL_WINDOWEVENT_FOCUS_GAINED/SDL_WINDOWEVENT_FOCUS_LOST. +// 2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using SDL_GetMouseFocus() + SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, requires SDL 2.0.5+) +// 2021-06-29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950) +// 2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends. +// 2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2). +// 2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state). +// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. +// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. +// 2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application). +// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized. +// 2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls. +// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. +// 2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'. +// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls. +// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. +// 2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples. +// 2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter. +// 2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText). +// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag. +// 2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS). +// 2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_sdl2.h" + +// Clang warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#endif + +// SDL +#include +#include +#if defined(__APPLE__) +#include +#endif + +#if SDL_VERSION_ATLEAST(2,0,4) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__) +#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 1 +#else +#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 0 +#endif +#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) + +// SDL Data +struct ImGui_ImplSDL2_Data +{ + SDL_Window* Window; + SDL_Renderer* Renderer; + Uint64 Time; + Uint32 MouseWindowID; + int MouseButtonsDown; + SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT]; + SDL_Cursor* LastMouseCursor; + int PendingMouseLeaveFrame; + char* ClipboardTextData; + bool MouseCanUseGlobalState; + + ImGui_ImplSDL2_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not well tested and probably dysfunctional in this backend. +// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context. +static ImGui_ImplSDL2_Data* ImGui_ImplSDL2_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; +} + +// Functions +static const char* ImGui_ImplSDL2_GetClipboardText(void*) +{ + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + if (bd->ClipboardTextData) + SDL_free(bd->ClipboardTextData); + bd->ClipboardTextData = SDL_GetClipboardText(); + return bd->ClipboardTextData; +} + +static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text) +{ + SDL_SetClipboardText(text); +} + +// Note: native IME will only display if user calls SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1") _before_ SDL_CreateWindow(). +static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport*, ImGuiPlatformImeData* data) +{ + if (data->WantVisible) + { + SDL_Rect r; + r.x = (int)data->InputPos.x; + r.y = (int)data->InputPos.y; + r.w = 1; + r.h = (int)data->InputLineHeight; + SDL_SetTextInputRect(&r); + } +} + +static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode) +{ + switch (keycode) + { + case SDLK_TAB: return ImGuiKey_Tab; + case SDLK_LEFT: return ImGuiKey_LeftArrow; + case SDLK_RIGHT: return ImGuiKey_RightArrow; + case SDLK_UP: return ImGuiKey_UpArrow; + case SDLK_DOWN: return ImGuiKey_DownArrow; + case SDLK_PAGEUP: return ImGuiKey_PageUp; + case SDLK_PAGEDOWN: return ImGuiKey_PageDown; + case SDLK_HOME: return ImGuiKey_Home; + case SDLK_END: return ImGuiKey_End; + case SDLK_INSERT: return ImGuiKey_Insert; + case SDLK_DELETE: return ImGuiKey_Delete; + case SDLK_BACKSPACE: return ImGuiKey_Backspace; + case SDLK_SPACE: return ImGuiKey_Space; + case SDLK_RETURN: return ImGuiKey_Enter; + case SDLK_ESCAPE: return ImGuiKey_Escape; + case SDLK_QUOTE: return ImGuiKey_Apostrophe; + case SDLK_COMMA: return ImGuiKey_Comma; + case SDLK_MINUS: return ImGuiKey_Minus; + case SDLK_PERIOD: return ImGuiKey_Period; + case SDLK_SLASH: return ImGuiKey_Slash; + case SDLK_SEMICOLON: return ImGuiKey_Semicolon; + case SDLK_EQUALS: return ImGuiKey_Equal; + case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket; + case SDLK_BACKSLASH: return ImGuiKey_Backslash; + case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket; + case SDLK_BACKQUOTE: return ImGuiKey_GraveAccent; + case SDLK_CAPSLOCK: return ImGuiKey_CapsLock; + case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock; + case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock; + case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen; + case SDLK_PAUSE: return ImGuiKey_Pause; + case SDLK_KP_0: return ImGuiKey_Keypad0; + case SDLK_KP_1: return ImGuiKey_Keypad1; + case SDLK_KP_2: return ImGuiKey_Keypad2; + case SDLK_KP_3: return ImGuiKey_Keypad3; + case SDLK_KP_4: return ImGuiKey_Keypad4; + case SDLK_KP_5: return ImGuiKey_Keypad5; + case SDLK_KP_6: return ImGuiKey_Keypad6; + case SDLK_KP_7: return ImGuiKey_Keypad7; + case SDLK_KP_8: return ImGuiKey_Keypad8; + case SDLK_KP_9: return ImGuiKey_Keypad9; + case SDLK_KP_PERIOD: return ImGuiKey_KeypadDecimal; + case SDLK_KP_DIVIDE: return ImGuiKey_KeypadDivide; + case SDLK_KP_MULTIPLY: return ImGuiKey_KeypadMultiply; + case SDLK_KP_MINUS: return ImGuiKey_KeypadSubtract; + case SDLK_KP_PLUS: return ImGuiKey_KeypadAdd; + case SDLK_KP_ENTER: return ImGuiKey_KeypadEnter; + case SDLK_KP_EQUALS: return ImGuiKey_KeypadEqual; + case SDLK_LCTRL: return ImGuiKey_LeftCtrl; + case SDLK_LSHIFT: return ImGuiKey_LeftShift; + case SDLK_LALT: return ImGuiKey_LeftAlt; + case SDLK_LGUI: return ImGuiKey_LeftSuper; + case SDLK_RCTRL: return ImGuiKey_RightCtrl; + case SDLK_RSHIFT: return ImGuiKey_RightShift; + case SDLK_RALT: return ImGuiKey_RightAlt; + case SDLK_RGUI: return ImGuiKey_RightSuper; + case SDLK_APPLICATION: return ImGuiKey_Menu; + case SDLK_0: return ImGuiKey_0; + case SDLK_1: return ImGuiKey_1; + case SDLK_2: return ImGuiKey_2; + case SDLK_3: return ImGuiKey_3; + case SDLK_4: return ImGuiKey_4; + case SDLK_5: return ImGuiKey_5; + case SDLK_6: return ImGuiKey_6; + case SDLK_7: return ImGuiKey_7; + case SDLK_8: return ImGuiKey_8; + case SDLK_9: return ImGuiKey_9; + case SDLK_a: return ImGuiKey_A; + case SDLK_b: return ImGuiKey_B; + case SDLK_c: return ImGuiKey_C; + case SDLK_d: return ImGuiKey_D; + case SDLK_e: return ImGuiKey_E; + case SDLK_f: return ImGuiKey_F; + case SDLK_g: return ImGuiKey_G; + case SDLK_h: return ImGuiKey_H; + case SDLK_i: return ImGuiKey_I; + case SDLK_j: return ImGuiKey_J; + case SDLK_k: return ImGuiKey_K; + case SDLK_l: return ImGuiKey_L; + case SDLK_m: return ImGuiKey_M; + case SDLK_n: return ImGuiKey_N; + case SDLK_o: return ImGuiKey_O; + case SDLK_p: return ImGuiKey_P; + case SDLK_q: return ImGuiKey_Q; + case SDLK_r: return ImGuiKey_R; + case SDLK_s: return ImGuiKey_S; + case SDLK_t: return ImGuiKey_T; + case SDLK_u: return ImGuiKey_U; + case SDLK_v: return ImGuiKey_V; + case SDLK_w: return ImGuiKey_W; + case SDLK_x: return ImGuiKey_X; + case SDLK_y: return ImGuiKey_Y; + case SDLK_z: return ImGuiKey_Z; + case SDLK_F1: return ImGuiKey_F1; + case SDLK_F2: return ImGuiKey_F2; + case SDLK_F3: return ImGuiKey_F3; + case SDLK_F4: return ImGuiKey_F4; + case SDLK_F5: return ImGuiKey_F5; + case SDLK_F6: return ImGuiKey_F6; + case SDLK_F7: return ImGuiKey_F7; + case SDLK_F8: return ImGuiKey_F8; + case SDLK_F9: return ImGuiKey_F9; + case SDLK_F10: return ImGuiKey_F10; + case SDLK_F11: return ImGuiKey_F11; + case SDLK_F12: return ImGuiKey_F12; + } + return ImGuiKey_None; +} + +static void ImGui_ImplSDL2_UpdateKeyModifiers(SDL_Keymod sdl_key_mods) +{ + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & KMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & KMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & KMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & KMOD_GUI) != 0); +} + +// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. +// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. +// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field. +bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + + switch (event->type) + { + case SDL_MOUSEMOTION: + { + ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y); + io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse); + io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); + return true; + } + case SDL_MOUSEWHEEL: + { + //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY); +#if SDL_VERSION_ATLEAST(2,0,18) // If this fails to compile on Emscripten: update to latest Emscripten! + float wheel_x = -event->wheel.preciseX; + float wheel_y = event->wheel.preciseY; +#else + float wheel_x = -(float)event->wheel.x; + float wheel_y = (float)event->wheel.y; +#endif +#ifdef __EMSCRIPTEN__ + wheel_x /= 100.0f; +#endif + io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse); + io.AddMouseWheelEvent(wheel_x, wheel_y); + return true; + } + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + int mouse_button = -1; + if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; } + if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; } + if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; } + if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; } + if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; } + if (mouse_button == -1) + break; + io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse); + io.AddMouseButtonEvent(mouse_button, (event->type == SDL_MOUSEBUTTONDOWN)); + bd->MouseButtonsDown = (event->type == SDL_MOUSEBUTTONDOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button)); + return true; + } + case SDL_TEXTINPUT: + { + io.AddInputCharactersUTF8(event->text.text); + return true; + } + case SDL_KEYDOWN: + case SDL_KEYUP: + { + ImGui_ImplSDL2_UpdateKeyModifiers((SDL_Keymod)event->key.keysym.mod); + ImGuiKey key = ImGui_ImplSDL2_KeycodeToImGuiKey(event->key.keysym.sym); + io.AddKeyEvent(key, (event->type == SDL_KEYDOWN)); + io.SetKeyEventNativeData(key, event->key.keysym.sym, event->key.keysym.scancode, event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions. + return true; + } + case SDL_WINDOWEVENT: + { + // - When capturing mouse, SDL will send a bunch of conflicting LEAVE/ENTER event on every mouse move, but the final ENTER tends to be right. + // - However we won't get a correct LEAVE event for a captured window. + // - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late, + // causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why + // we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details. + Uint8 window_event = event->window.event; + if (window_event == SDL_WINDOWEVENT_ENTER) + { + bd->MouseWindowID = event->window.windowID; + bd->PendingMouseLeaveFrame = 0; + } + if (window_event == SDL_WINDOWEVENT_LEAVE) + bd->PendingMouseLeaveFrame = ImGui::GetFrameCount() + 1; + if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED) + io.AddFocusEvent(true); + else if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) + io.AddFocusEvent(false); + return true; + } + } + return false; +} + +static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer) +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + + // Check and store if we are on a SDL backend that supports global mouse position + // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) + bool mouse_can_use_global_state = false; +#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE + const char* sdl_backend = SDL_GetCurrentVideoDriver(); + const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; + for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++) + if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0) + mouse_can_use_global_state = true; +#endif + + // Setup backend capabilities flags + ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_sdl2"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + + bd->Window = window; + bd->Renderer = renderer; + bd->MouseCanUseGlobalState = mouse_can_use_global_state; + + io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText; + io.ClipboardUserData = nullptr; + io.SetPlatformImeDataFn = ImGui_ImplSDL2_SetPlatformImeData; + + // Load mouse cursors + bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + + // Set platform dependent data in viewport + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->PlatformHandleRaw = nullptr; + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if (SDL_GetWindowWMInfo(window, &info)) + { +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + main_viewport->PlatformHandleRaw = (void*)info.info.win.window; +#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA) + main_viewport->PlatformHandleRaw = (void*)info.info.cocoa.window; +#endif + } + + // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event. + // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered. + // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application. + // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click: + // you can ignore SDL_MOUSEBUTTONDOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) +#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); +#endif + + // From 2.0.18: Enable native IME. + // IMPORTANT: This is used at the time of SDL_CreateWindow() so this will only affects secondary windows, if any. + // For the main window to be affected, your application needs to call this manually before calling SDL_CreateWindow(). +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710) +#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); +#endif + + return true; +} + +bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context) +{ + IM_UNUSED(sdl_gl_context); // Viewport branch will need this. + return ImGui_ImplSDL2_Init(window, nullptr); +} + +bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window) +{ +#if !SDL_HAS_VULKAN + IM_ASSERT(0 && "Unsupported"); +#endif + return ImGui_ImplSDL2_Init(window, nullptr); +} + +bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window) +{ +#if !defined(_WIN32) + IM_ASSERT(0 && "Unsupported"); +#endif + return ImGui_ImplSDL2_Init(window, nullptr); +} + +bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window) +{ + return ImGui_ImplSDL2_Init(window, nullptr); +} + +bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer) +{ + return ImGui_ImplSDL2_Init(window, renderer); +} + +bool ImGui_ImplSDL2_InitForOther(SDL_Window* window) +{ + return ImGui_ImplSDL2_Init(window, nullptr); +} + +void ImGui_ImplSDL2_Shutdown() +{ + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + if (bd->ClipboardTextData) + SDL_free(bd->ClipboardTextData); + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) + SDL_FreeCursor(bd->MouseCursors[cursor_n]); + bd->LastMouseCursor = nullptr; + + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad); + IM_DELETE(bd); +} + +static void ImGui_ImplSDL2_UpdateMouseData() +{ + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + // We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below) +#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE + // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside + SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE); + SDL_Window* focused_window = SDL_GetKeyboardFocus(); + const bool is_app_focused = (bd->Window == focused_window); +#else + const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only +#endif + if (is_app_focused) + { + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + SDL_WarpMouseInWindow(bd->Window, (int)io.MousePos.x, (int)io.MousePos.y); + + // (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured) + if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0) + { + int window_x, window_y, mouse_x_global, mouse_y_global; + SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); + SDL_GetWindowPosition(bd->Window, &window_x, &window_y); + io.AddMousePosEvent((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y)); + } + } +} + +static void ImGui_ImplSDL2_UpdateMouseCursor() +{ + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return; + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) + { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + SDL_ShowCursor(SDL_FALSE); + } + else + { + // Show OS mouse cursor + SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]; + if (bd->LastMouseCursor != expected_cursor) + { + SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113) + bd->LastMouseCursor = expected_cursor; + } + SDL_ShowCursor(SDL_TRUE); + } +} + +static void ImGui_ImplSDL2_UpdateGamepads() +{ + ImGuiIO& io = ImGui::GetIO(); + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. + return; + + // Get gamepad + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + SDL_GameController* game_controller = SDL_GameControllerOpen(0); + if (!game_controller) + return; + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + // Update gamepad inputs + #define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V) + #define MAP_BUTTON(KEY_NO, BUTTON_NO) { io.AddKeyEvent(KEY_NO, SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0); } + #define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); } + const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value. + MAP_BUTTON(ImGuiKey_GamepadStart, SDL_CONTROLLER_BUTTON_START); + MAP_BUTTON(ImGuiKey_GamepadBack, SDL_CONTROLLER_BUTTON_BACK); + MAP_BUTTON(ImGuiKey_GamepadFaceLeft, SDL_CONTROLLER_BUTTON_X); // Xbox X, PS Square + MAP_BUTTON(ImGuiKey_GamepadFaceRight, SDL_CONTROLLER_BUTTON_B); // Xbox B, PS Circle + MAP_BUTTON(ImGuiKey_GamepadFaceUp, SDL_CONTROLLER_BUTTON_Y); // Xbox Y, PS Triangle + MAP_BUTTON(ImGuiKey_GamepadFaceDown, SDL_CONTROLLER_BUTTON_A); // Xbox A, PS Cross + MAP_BUTTON(ImGuiKey_GamepadDpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT); + MAP_BUTTON(ImGuiKey_GamepadDpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + MAP_BUTTON(ImGuiKey_GamepadDpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP); + MAP_BUTTON(ImGuiKey_GamepadDpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN); + MAP_BUTTON(ImGuiKey_GamepadL1, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + MAP_BUTTON(ImGuiKey_GamepadR1, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + MAP_ANALOG(ImGuiKey_GamepadL2, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 0.0f, 32767); + MAP_ANALOG(ImGuiKey_GamepadR2, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 0.0f, 32767); + MAP_BUTTON(ImGuiKey_GamepadL3, SDL_CONTROLLER_BUTTON_LEFTSTICK); + MAP_BUTTON(ImGuiKey_GamepadR3, SDL_CONTROLLER_BUTTON_RIGHTSTICK); + MAP_ANALOG(ImGuiKey_GamepadLStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768); + MAP_ANALOG(ImGuiKey_GamepadLStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767); + MAP_ANALOG(ImGuiKey_GamepadLStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32768); + MAP_ANALOG(ImGuiKey_GamepadLStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767); + MAP_ANALOG(ImGuiKey_GamepadRStickLeft, SDL_CONTROLLER_AXIS_RIGHTX, -thumb_dead_zone, -32768); + MAP_ANALOG(ImGuiKey_GamepadRStickRight, SDL_CONTROLLER_AXIS_RIGHTX, +thumb_dead_zone, +32767); + MAP_ANALOG(ImGuiKey_GamepadRStickUp, SDL_CONTROLLER_AXIS_RIGHTY, -thumb_dead_zone, -32768); + MAP_ANALOG(ImGuiKey_GamepadRStickDown, SDL_CONTROLLER_AXIS_RIGHTY, +thumb_dead_zone, +32767); + #undef MAP_BUTTON + #undef MAP_ANALOG +} + +void ImGui_ImplSDL2_NewFrame() +{ + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplSDL2_Init()?"); + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + SDL_GetWindowSize(bd->Window, &w, &h); + if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED) + w = h = 0; + if (bd->Renderer != nullptr) + SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h); + else + SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) + io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + + // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) + // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644) + static Uint64 frequency = SDL_GetPerformanceFrequency(); + Uint64 current_time = SDL_GetPerformanceCounter(); + if (current_time <= bd->Time) + current_time = bd->Time + 1; + io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f); + bd->Time = current_time; + + if (bd->PendingMouseLeaveFrame && bd->PendingMouseLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0) + { + bd->MouseWindowID = 0; + bd->PendingMouseLeaveFrame = 0; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + } + + ImGui_ImplSDL2_UpdateMouseData(); + ImGui_ImplSDL2_UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + ImGui_ImplSDL2_UpdateGamepads(); +} + +//----------------------------------------------------------------------------- + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/src/imgui/imgui_impl_sdl2.h b/src/imgui/imgui_impl_sdl2.h new file mode 100644 index 0000000..dd5e047 --- /dev/null +++ b/src/imgui/imgui_impl_sdl2.h @@ -0,0 +1,43 @@ +// dear imgui: Platform Backend for SDL2 +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) +// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) + +// Implemented features: +// [X] Platform: Clipboard support. +// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen. +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +struct SDL_Window; +struct SDL_Renderer; +typedef union SDL_Event SDL_Event; + +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer); +IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOther(SDL_Window* window); +IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static inline void ImGui_ImplSDL2_NewFrame(SDL_Window*) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/src/imgui/imgui_impl_vulkan.cpp b/src/imgui/imgui_impl_vulkan.cpp new file mode 100644 index 0000000..96d4cc1 --- /dev/null +++ b/src/imgui/imgui_impl_vulkan.cpp @@ -0,0 +1,1474 @@ +// dear imgui: Renderer Backend for Vulkan +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. +// Missing features: +// [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this backend! See https://github.com/ocornut/imgui/pull/914 + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. +// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ + +// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. +// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. +// You will use those if you want to use this rendering backend in your engine/app. +// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by +// the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. +// Read comments in imgui_impl_vulkan.h. + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2021-10-15: Vulkan: Call vkCmdSetScissor() at the end of render a full-viewport to reduce likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling vkCmdSetScissor() explicitly every frame. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-03-22: Vulkan: Fix mapped memory validation error when buffer sizes are not multiple of VkPhysicalDeviceLimits::nonCoherentAtomSize. +// 2021-02-18: Vulkan: Change blending equation to preserve alpha in output buffer. +// 2021-01-27: Vulkan: Added support for custom function load and IMGUI_IMPL_VULKAN_NO_PROTOTYPES by using ImGui_ImplVulkan_LoadFunctions(). +// 2020-11-11: Vulkan: Added support for specifying which subpass to reference during VkPipeline creation. +// 2020-09-07: Vulkan: Added VkPipeline parameter to ImGui_ImplVulkan_RenderDrawData (default to one passed to ImGui_ImplVulkan_Init). +// 2020-05-04: Vulkan: Fixed crash if initial frame has no vertices. +// 2020-04-26: Vulkan: Fixed edge case where render callbacks wouldn't be called if the ImDrawData didn't have vertices. +// 2019-08-01: Vulkan: Added support for specifying multisample count. Set ImGui_ImplVulkan_InitInfo::MSAASamples to one of the VkSampleCountFlagBits values to use, default is non-multisampled as before. +// 2019-05-29: Vulkan: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: Vulkan: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-04-04: *BREAKING CHANGE*: Vulkan: Added ImageCount/MinImageCount fields in ImGui_ImplVulkan_InitInfo, required for initialization (was previously a hard #define IMGUI_VK_QUEUED_FRAMES 2). Added ImGui_ImplVulkan_SetMinImageCount(). +// 2019-04-04: Vulkan: Added VkInstance argument to ImGui_ImplVulkanH_CreateWindow() optional helper. +// 2019-04-04: Vulkan: Avoid passing negative coordinates to vkCmdSetScissor, which debug validation layers do not like. +// 2019-04-01: Vulkan: Support for 32-bit index buffer (#define ImDrawIdx unsigned int). +// 2019-02-16: Vulkan: Viewport and clipping rectangles correctly using draw_data->FramebufferScale to allow retina display. +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-08-25: Vulkan: Fixed mishandled VkSurfaceCapabilitiesKHR::maxImageCount=0 case. +// 2018-06-22: Inverted the parameters to ImGui_ImplVulkan_RenderDrawData() to be consistent with other backends. +// 2018-06-08: Misc: Extracted imgui_impl_vulkan.cpp/.h away from the old combined GLFW+Vulkan example. +// 2018-06-08: Vulkan: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-03-03: Vulkan: Various refactor, created a couple of ImGui_ImplVulkanH_XXX helper that the example can use and that viewport support will use. +// 2018-03-01: Vulkan: Renamed ImGui_ImplVulkan_Init_Info to ImGui_ImplVulkan_InitInfo and fields to match more closely Vulkan terminology. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback, ImGui_ImplVulkan_Render() calls ImGui_ImplVulkan_RenderDrawData() itself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2017-05-15: Vulkan: Fix scissor offset being negative. Fix new Vulkan validation warnings. Set required depth member for buffer image copy. +// 2016-11-13: Vulkan: Fix validation layer warnings and errors and redeclare gl_PerVertex. +// 2016-10-18: Vulkan: Add location decorators & change to use structs as in/out in glsl, update embedded spv (produced with glslangValidator -x). Null the released resources. +// 2016-08-27: Vulkan: Fix Vulkan example for use when a depth buffer is active. + +#include "imgui_impl_vulkan.h" +#include + +// Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() +// [Please zero-clear before use!] +struct ImGui_ImplVulkanH_FrameRenderBuffers +{ + VkDeviceMemory VertexBufferMemory; + VkDeviceMemory IndexBufferMemory; + VkDeviceSize VertexBufferSize; + VkDeviceSize IndexBufferSize; + VkBuffer VertexBuffer; + VkBuffer IndexBuffer; +}; + +// Each viewport will hold 1 ImGui_ImplVulkanH_WindowRenderBuffers +// [Please zero-clear before use!] +struct ImGui_ImplVulkanH_WindowRenderBuffers +{ + uint32_t Index; + uint32_t Count; + ImGui_ImplVulkanH_FrameRenderBuffers* FrameRenderBuffers; +}; + +// Vulkan data +struct ImGui_ImplVulkan_Data +{ + ImGui_ImplVulkan_InitInfo VulkanInitInfo; + VkRenderPass RenderPass; + VkDeviceSize BufferMemoryAlignment; + VkPipelineCreateFlags PipelineCreateFlags; + VkDescriptorSetLayout DescriptorSetLayout; + VkPipelineLayout PipelineLayout; + VkDescriptorSet DescriptorSet; + VkPipeline Pipeline; + uint32_t Subpass; + VkShaderModule ShaderModuleVert; + VkShaderModule ShaderModuleFrag; + + // Font data + VkSampler FontSampler; + VkDeviceMemory FontMemory; + VkImage FontImage; + VkImageView FontView; + VkDeviceMemory UploadBufferMemory; + VkBuffer UploadBuffer; + + // Render buffers + ImGui_ImplVulkanH_WindowRenderBuffers MainWindowRenderBuffers; + + ImGui_ImplVulkan_Data() + { + memset(this, 0, sizeof(*this)); + BufferMemoryAlignment = 256; + } +}; + +// Forward Declarations +bool ImGui_ImplVulkan_CreateDeviceObjects(); +void ImGui_ImplVulkan_DestroyDeviceObjects(); +void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); +void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator); + +// Vulkan prototypes for use with custom loaders +// (see description of IMGUI_IMPL_VULKAN_NO_PROTOTYPES in imgui_impl_vulkan.h +#if defined(VK_NO_PROTOTYPES) && !defined(VOLK_HEADER_VERSION) +static bool g_FunctionsLoaded = false; +#else +static bool g_FunctionsLoaded = true; +#endif +#if defined(VK_NO_PROTOTYPES) && !defined(VOLK_HEADER_VERSION) +#define IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_MAP_MACRO) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateCommandBuffers) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateDescriptorSets) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateMemory) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkBindBufferMemory) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkBindImageMemory) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdBindDescriptorSets) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdBindIndexBuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdBindPipeline) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdBindVertexBuffers) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdCopyBufferToImage) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdDrawIndexed) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPipelineBarrier) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPushConstants) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdSetScissor) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdSetViewport) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateBuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateCommandPool) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateDescriptorSetLayout) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateFence) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateFramebuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateGraphicsPipelines) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateImage) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateImageView) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreatePipelineLayout) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateRenderPass) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateSampler) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateSemaphore) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateShaderModule) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateSwapchainKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyBuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyCommandPool) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyDescriptorSetLayout) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyFence) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyFramebuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyImage) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyImageView) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyPipeline) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyPipelineLayout) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyRenderPass) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroySampler) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroySemaphore) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroyShaderModule) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroySurfaceKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDestroySwapchainKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkDeviceWaitIdle) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkFlushMappedMemoryRanges) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeCommandBuffers) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeMemory) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetBufferMemoryRequirements) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetImageMemoryRequirements) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceMemoryProperties) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceSurfaceCapabilitiesKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceSurfaceFormatsKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceSurfacePresentModesKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetSwapchainImagesKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkMapMemory) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkUnmapMemory) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkUpdateDescriptorSets) + +// Define function pointers +#define IMGUI_VULKAN_FUNC_DEF(func) static PFN_##func func; +IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_DEF) +#undef IMGUI_VULKAN_FUNC_DEF +#endif // VK_NO_PROTOTYPES + +//----------------------------------------------------------------------------- +// SHADERS +//----------------------------------------------------------------------------- + +// glsl_shader.vert, compiled with: +// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert +/* +#version 450 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec4 aColor; +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; + +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; + +void main() +{ + Out.Color = aColor; + Out.UV = aUV; + gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); +} +*/ +static uint32_t __glsl_shader_vert_spv[] = +{ + 0x07230203,0x00010000,0x00080001,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015, + 0x0000001b,0x0000001c,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00030005,0x00000009,0x00000000,0x00050006,0x00000009,0x00000000,0x6f6c6f43, + 0x00000072,0x00040006,0x00000009,0x00000001,0x00005655,0x00030005,0x0000000b,0x0074754f, + 0x00040005,0x0000000f,0x6c6f4361,0x0000726f,0x00030005,0x00000015,0x00565561,0x00060005, + 0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019,0x00000000, + 0x505f6c67,0x7469736f,0x006e6f69,0x00030005,0x0000001b,0x00000000,0x00040005,0x0000001c, + 0x736f5061,0x00000000,0x00060005,0x0000001e,0x73755075,0x6e6f4368,0x6e617473,0x00000074, + 0x00050006,0x0000001e,0x00000000,0x61635375,0x0000656c,0x00060006,0x0000001e,0x00000001, + 0x61725475,0x616c736e,0x00006574,0x00030005,0x00000020,0x00006370,0x00040047,0x0000000b, + 0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015, + 0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047, + 0x00000019,0x00000002,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00050048,0x0000001e, + 0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000008, + 0x00030047,0x0000001e,0x00000002,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040017, + 0x00000008,0x00000006,0x00000002,0x0004001e,0x00000009,0x00000007,0x00000008,0x00040020, + 0x0000000a,0x00000003,0x00000009,0x0004003b,0x0000000a,0x0000000b,0x00000003,0x00040015, + 0x0000000c,0x00000020,0x00000001,0x0004002b,0x0000000c,0x0000000d,0x00000000,0x00040020, + 0x0000000e,0x00000001,0x00000007,0x0004003b,0x0000000e,0x0000000f,0x00000001,0x00040020, + 0x00000011,0x00000003,0x00000007,0x0004002b,0x0000000c,0x00000013,0x00000001,0x00040020, + 0x00000014,0x00000001,0x00000008,0x0004003b,0x00000014,0x00000015,0x00000001,0x00040020, + 0x00000017,0x00000003,0x00000008,0x0003001e,0x00000019,0x00000007,0x00040020,0x0000001a, + 0x00000003,0x00000019,0x0004003b,0x0000001a,0x0000001b,0x00000003,0x0004003b,0x00000014, + 0x0000001c,0x00000001,0x0004001e,0x0000001e,0x00000008,0x00000008,0x00040020,0x0000001f, + 0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009,0x00040020,0x00000021, + 0x00000009,0x00000008,0x0004002b,0x00000006,0x00000028,0x00000000,0x0004002b,0x00000006, + 0x00000029,0x3f800000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, + 0x00000005,0x0004003d,0x00000007,0x00000010,0x0000000f,0x00050041,0x00000011,0x00000012, + 0x0000000b,0x0000000d,0x0003003e,0x00000012,0x00000010,0x0004003d,0x00000008,0x00000016, + 0x00000015,0x00050041,0x00000017,0x00000018,0x0000000b,0x00000013,0x0003003e,0x00000018, + 0x00000016,0x0004003d,0x00000008,0x0000001d,0x0000001c,0x00050041,0x00000021,0x00000022, + 0x00000020,0x0000000d,0x0004003d,0x00000008,0x00000023,0x00000022,0x00050085,0x00000008, + 0x00000024,0x0000001d,0x00000023,0x00050041,0x00000021,0x00000025,0x00000020,0x00000013, + 0x0004003d,0x00000008,0x00000026,0x00000025,0x00050081,0x00000008,0x00000027,0x00000024, + 0x00000026,0x00050051,0x00000006,0x0000002a,0x00000027,0x00000000,0x00050051,0x00000006, + 0x0000002b,0x00000027,0x00000001,0x00070050,0x00000007,0x0000002c,0x0000002a,0x0000002b, + 0x00000028,0x00000029,0x00050041,0x00000011,0x0000002d,0x0000001b,0x0000000d,0x0003003e, + 0x0000002d,0x0000002c,0x000100fd,0x00010038 +}; + +// glsl_shader.frag, compiled with: +// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag +/* +#version 450 core +layout(location = 0) out vec4 fColor; +layout(set=0, binding=0) uniform sampler2D sTexture; +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; +void main() +{ + fColor = In.Color * texture(sTexture, In.UV.st); +} +*/ +static uint32_t __glsl_shader_frag_spv[] = +{ + 0x07230203,0x00010000,0x00080001,0x0000001e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000, + 0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001, + 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000016,0x78655473,0x65727574, + 0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e, + 0x00000000,0x00040047,0x00000016,0x00000022,0x00000000,0x00040047,0x00000016,0x00000021, + 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, + 0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003, + 0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006, + 0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001, + 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020, + 0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001, + 0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, + 0x00000001,0x00000000,0x0003001b,0x00000014,0x00000013,0x00040020,0x00000015,0x00000000, + 0x00000014,0x0004003b,0x00000015,0x00000016,0x00000000,0x0004002b,0x0000000e,0x00000018, + 0x00000001,0x00040020,0x00000019,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004, + 0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d, + 0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000014,0x00000017, + 0x00000016,0x00050041,0x00000019,0x0000001a,0x0000000d,0x00000018,0x0004003d,0x0000000a, + 0x0000001b,0x0000001a,0x00050057,0x00000007,0x0000001c,0x00000017,0x0000001b,0x00050085, + 0x00000007,0x0000001d,0x00000012,0x0000001c,0x0003003e,0x00000009,0x0000001d,0x000100fd, + 0x00010038 +}; + +//----------------------------------------------------------------------------- +// FUNCTIONS +//----------------------------------------------------------------------------- + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not tested and probably dysfunctional in this backend. +static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} + +static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + VkPhysicalDeviceMemoryProperties prop; + vkGetPhysicalDeviceMemoryProperties(v->PhysicalDevice, &prop); + for (uint32_t i = 0; i < prop.memoryTypeCount; i++) + if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i)) + return i; + return 0xFFFFFFFF; // Unable to find memoryType +} + +static void check_vk_result(VkResult err) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (!bd) + return; + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + if (v->CheckVkResultFn) + v->CheckVkResultFn(err); +} + +static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + VkResult err; + if (buffer != VK_NULL_HANDLE) + vkDestroyBuffer(v->Device, buffer, v->Allocator); + if (buffer_memory != VK_NULL_HANDLE) + vkFreeMemory(v->Device, buffer_memory, v->Allocator); + + VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / bd->BufferMemoryAlignment + 1) * bd->BufferMemoryAlignment; + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = vertex_buffer_size_aligned; + buffer_info.usage = usage; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &buffer); + check_vk_result(err); + + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(v->Device, buffer, &req); + bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &buffer_memory); + check_vk_result(err); + + err = vkBindBufferMemory(v->Device, buffer, buffer_memory, 0); + check_vk_result(err); + p_buffer_size = req.size; +} + +static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline pipeline, VkCommandBuffer command_buffer, ImGui_ImplVulkanH_FrameRenderBuffers* rb, int fb_width, int fb_height) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + + // Bind pipeline and descriptor sets: + { + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + VkDescriptorSet desc_set[1] = { bd->DescriptorSet }; + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, desc_set, 0, NULL); + } + + // Bind Vertex And Index Buffer: + if (draw_data->TotalVtxCount > 0) + { + VkBuffer vertex_buffers[1] = { rb->VertexBuffer }; + VkDeviceSize vertex_offset[1] = { 0 }; + vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, vertex_offset); + vkCmdBindIndexBuffer(command_buffer, rb->IndexBuffer, 0, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); + } + + // Setup viewport: + { + VkViewport viewport; + viewport.x = 0; + viewport.y = 0; + viewport.width = (float)fb_width; + viewport.height = (float)fb_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(command_buffer, 0, 1, &viewport); + } + + // Setup scale and translation: + // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + { + float scale[2]; + scale[0] = 2.0f / draw_data->DisplaySize.x; + scale[1] = 2.0f / draw_data->DisplaySize.y; + float translate[2]; + translate[0] = -1.0f - draw_data->DisplayPos.x * scale[0]; + translate[1] = -1.0f - draw_data->DisplayPos.y * scale[1]; + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); + } +} + +// Render function +void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) + return; + + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + if (pipeline == VK_NULL_HANDLE) + pipeline = bd->Pipeline; + + // Allocate array to store enough vertex/index buffers + ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &bd->MainWindowRenderBuffers; + if (wrb->FrameRenderBuffers == NULL) + { + wrb->Index = 0; + wrb->Count = v->ImageCount; + wrb->FrameRenderBuffers = (ImGui_ImplVulkanH_FrameRenderBuffers*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); + memset(wrb->FrameRenderBuffers, 0, sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); + } + IM_ASSERT(wrb->Count == v->ImageCount); + wrb->Index = (wrb->Index + 1) % wrb->Count; + ImGui_ImplVulkanH_FrameRenderBuffers* rb = &wrb->FrameRenderBuffers[wrb->Index]; + + if (draw_data->TotalVtxCount > 0) + { + // Create or resize the vertex/index buffers + size_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); + if (rb->VertexBuffer == VK_NULL_HANDLE || rb->VertexBufferSize < vertex_size) + CreateOrResizeBuffer(rb->VertexBuffer, rb->VertexBufferMemory, rb->VertexBufferSize, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + if (rb->IndexBuffer == VK_NULL_HANDLE || rb->IndexBufferSize < index_size) + CreateOrResizeBuffer(rb->IndexBuffer, rb->IndexBufferMemory, rb->IndexBufferSize, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + + // Upload vertex/index data into a single contiguous GPU buffer + ImDrawVert* vtx_dst = NULL; + ImDrawIdx* idx_dst = NULL; + VkResult err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, rb->VertexBufferSize, 0, (void**)(&vtx_dst)); + check_vk_result(err); + err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, rb->IndexBufferSize, 0, (void**)(&idx_dst)); + check_vk_result(err); + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + vtx_dst += cmd_list->VtxBuffer.Size; + idx_dst += cmd_list->IdxBuffer.Size; + } + VkMappedMemoryRange range[2] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = rb->VertexBufferMemory; + range[0].size = VK_WHOLE_SIZE; + range[1].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[1].memory = rb->IndexBufferMemory; + range[1].size = VK_WHOLE_SIZE; + err = vkFlushMappedMemoryRanges(v->Device, 2, range); + check_vk_result(err); + vkUnmapMemory(v->Device, rb->VertexBufferMemory); + vkUnmapMemory(v->Device, rb->IndexBufferMemory); + } + + // Setup desired Vulkan state + ImGui_ImplVulkan_SetupRenderState(draw_data, pipeline, command_buffer, rb, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int global_vtx_offset = 0; + int global_idx_offset = 0; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != NULL) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplVulkan_SetupRenderState(draw_data, pipeline, command_buffer, rb, fb_width, fb_height); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + + // Clamp to viewport as vkCmdSetScissor() won't accept values that are off bounds + if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } + if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } + if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle + VkRect2D scissor; + scissor.offset.x = (int32_t)(clip_min.x); + scissor.offset.y = (int32_t)(clip_min.y); + scissor.extent.width = (uint32_t)(clip_max.x - clip_min.x); + scissor.extent.height = (uint32_t)(clip_max.y - clip_min.y); + vkCmdSetScissor(command_buffer, 0, 1, &scissor); + + // Draw + vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); + } + } + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + + // Note: at this point both vkCmdSetViewport() and vkCmdSetScissor() have been called. + // Our last values will leak into user/application rendering IF: + // - Your app uses a pipeline with VK_DYNAMIC_STATE_VIEWPORT or VK_DYNAMIC_STATE_SCISSOR dynamic state + // - And you forgot to call vkCmdSetViewport() and vkCmdSetScissor() yourself to explicitely set that state. + // If you use VK_DYNAMIC_STATE_VIEWPORT or VK_DYNAMIC_STATE_SCISSOR you are responsible for setting the values before rendering. + // In theory we should aim to backup/restore those values but I am not sure this is possible. + // We perform a call to vkCmdSetScissor() to set back a full viewport which is likely to fix things for 99% users but technically this is not perfect. (See github #4644) + VkRect2D scissor = { { 0, 0 }, { (uint32_t)fb_width, (uint32_t)fb_height } }; + vkCmdSetScissor(command_buffer, 0, 1, &scissor); +} + +bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + size_t upload_size = width * height * 4 * sizeof(char); + + VkResult err; + + // Create the Image: + { + VkImageCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.extent.width = width; + info.extent.height = height; + info.extent.depth = 1; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + err = vkCreateImage(v->Device, &info, v->Allocator, &bd->FontImage); + check_vk_result(err); + VkMemoryRequirements req; + vkGetImageMemoryRequirements(v->Device, bd->FontImage, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &bd->FontMemory); + check_vk_result(err); + err = vkBindImageMemory(v->Device, bd->FontImage, bd->FontMemory, 0); + check_vk_result(err); + } + + // Create the Image View: + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = bd->FontImage; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.levelCount = 1; + info.subresourceRange.layerCount = 1; + err = vkCreateImageView(v->Device, &info, v->Allocator, &bd->FontView); + check_vk_result(err); + } + + // Update the Descriptor Set: + { + VkDescriptorImageInfo desc_image[1] = {}; + desc_image[0].sampler = bd->FontSampler; + desc_image[0].imageView = bd->FontView; + desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + VkWriteDescriptorSet write_desc[1] = {}; + write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_desc[0].dstSet = bd->DescriptorSet; + write_desc[0].descriptorCount = 1; + write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_desc[0].pImageInfo = desc_image; + vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL); + } + + // Create the Upload Buffer: + { + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = upload_size; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &bd->UploadBuffer); + check_vk_result(err); + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(v->Device, bd->UploadBuffer, &req); + bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &bd->UploadBufferMemory); + check_vk_result(err); + err = vkBindBufferMemory(v->Device, bd->UploadBuffer, bd->UploadBufferMemory, 0); + check_vk_result(err); + } + + // Upload to Buffer: + { + char* map = NULL; + err = vkMapMemory(v->Device, bd->UploadBufferMemory, 0, upload_size, 0, (void**)(&map)); + check_vk_result(err); + memcpy(map, pixels, upload_size); + VkMappedMemoryRange range[1] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = bd->UploadBufferMemory; + range[0].size = upload_size; + err = vkFlushMappedMemoryRanges(v->Device, 1, range); + check_vk_result(err); + vkUnmapMemory(v->Device, bd->UploadBufferMemory); + } + + // Copy to Image: + { + VkImageMemoryBarrier copy_barrier[1] = {}; + copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].image = bd->FontImage; + copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_barrier[0].subresourceRange.levelCount = 1; + copy_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = width; + region.imageExtent.height = height; + region.imageExtent.depth = 1; + vkCmdCopyBufferToImage(command_buffer, bd->UploadBuffer, bd->FontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + VkImageMemoryBarrier use_barrier[1] = {}; + use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].image = bd->FontImage; + use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + use_barrier[0].subresourceRange.levelCount = 1; + use_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); + } + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontImage); + + return true; +} + +static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator) +{ + // Create the shader modules + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->ShaderModuleVert == VK_NULL_HANDLE) + { + VkShaderModuleCreateInfo vert_info = {}; + vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + vert_info.codeSize = sizeof(__glsl_shader_vert_spv); + vert_info.pCode = (uint32_t*)__glsl_shader_vert_spv; + VkResult err = vkCreateShaderModule(device, &vert_info, allocator, &bd->ShaderModuleVert); + check_vk_result(err); + } + if (bd->ShaderModuleFrag == VK_NULL_HANDLE) + { + VkShaderModuleCreateInfo frag_info = {}; + frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + frag_info.codeSize = sizeof(__glsl_shader_frag_spv); + frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; + VkResult err = vkCreateShaderModule(device, &frag_info, allocator, &bd->ShaderModuleFrag); + check_vk_result(err); + } +} + +static void ImGui_ImplVulkan_CreateFontSampler(VkDevice device, const VkAllocationCallbacks* allocator) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->FontSampler) + return; + + VkSamplerCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + info.magFilter = VK_FILTER_LINEAR; + info.minFilter = VK_FILTER_LINEAR; + info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.minLod = -1000; + info.maxLod = 1000; + info.maxAnisotropy = 1.0f; + VkResult err = vkCreateSampler(device, &info, allocator, &bd->FontSampler); + check_vk_result(err); +} + +static void ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDevice device, const VkAllocationCallbacks* allocator) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->DescriptorSetLayout) + return; + + ImGui_ImplVulkan_CreateFontSampler(device, allocator); + VkSampler sampler[1] = { bd->FontSampler }; + VkDescriptorSetLayoutBinding binding[1] = {}; + binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + binding[0].descriptorCount = 1; + binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + binding[0].pImmutableSamplers = sampler; + VkDescriptorSetLayoutCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.bindingCount = 1; + info.pBindings = binding; + VkResult err = vkCreateDescriptorSetLayout(device, &info, allocator, &bd->DescriptorSetLayout); + check_vk_result(err); +} + +static void ImGui_ImplVulkan_CreatePipelineLayout(VkDevice device, const VkAllocationCallbacks* allocator) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->PipelineLayout) + return; + + // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix + ImGui_ImplVulkan_CreateDescriptorSetLayout(device, allocator); + VkPushConstantRange push_constants[1] = {}; + push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + push_constants[0].offset = sizeof(float) * 0; + push_constants[0].size = sizeof(float) * 4; + VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; + VkPipelineLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_info.setLayoutCount = 1; + layout_info.pSetLayouts = set_layout; + layout_info.pushConstantRangeCount = 1; + layout_info.pPushConstantRanges = push_constants; + VkResult err = vkCreatePipelineLayout(device, &layout_info, allocator, &bd->PipelineLayout); + check_vk_result(err); +} + +static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline* pipeline, uint32_t subpass) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_CreateShaderModules(device, allocator); + + VkPipelineShaderStageCreateInfo stage[2] = {}; + stage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stage[0].module = bd->ShaderModuleVert; + stage[0].pName = "main"; + stage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stage[1].module = bd->ShaderModuleFrag; + stage[1].pName = "main"; + + VkVertexInputBindingDescription binding_desc[1] = {}; + binding_desc[0].stride = sizeof(ImDrawVert); + binding_desc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription attribute_desc[3] = {}; + attribute_desc[0].location = 0; + attribute_desc[0].binding = binding_desc[0].binding; + attribute_desc[0].format = VK_FORMAT_R32G32_SFLOAT; + attribute_desc[0].offset = IM_OFFSETOF(ImDrawVert, pos); + attribute_desc[1].location = 1; + attribute_desc[1].binding = binding_desc[0].binding; + attribute_desc[1].format = VK_FORMAT_R32G32_SFLOAT; + attribute_desc[1].offset = IM_OFFSETOF(ImDrawVert, uv); + attribute_desc[2].location = 2; + attribute_desc[2].binding = binding_desc[0].binding; + attribute_desc[2].format = VK_FORMAT_R8G8B8A8_UNORM; + attribute_desc[2].offset = IM_OFFSETOF(ImDrawVert, col); + + VkPipelineVertexInputStateCreateInfo vertex_info = {}; + vertex_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_info.vertexBindingDescriptionCount = 1; + vertex_info.pVertexBindingDescriptions = binding_desc; + vertex_info.vertexAttributeDescriptionCount = 3; + vertex_info.pVertexAttributeDescriptions = attribute_desc; + + VkPipelineInputAssemblyStateCreateInfo ia_info = {}; + ia_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkPipelineViewportStateCreateInfo viewport_info = {}; + viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_info.viewportCount = 1; + viewport_info.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo raster_info = {}; + raster_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + raster_info.polygonMode = VK_POLYGON_MODE_FILL; + raster_info.cullMode = VK_CULL_MODE_NONE; + raster_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + raster_info.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms_info = {}; + ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms_info.rasterizationSamples = (MSAASamples != 0) ? MSAASamples : VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState color_attachment[1] = {}; + color_attachment[0].blendEnable = VK_TRUE; + color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_attachment[0].colorBlendOp = VK_BLEND_OP_ADD; + color_attachment[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_attachment[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_attachment[0].alphaBlendOp = VK_BLEND_OP_ADD; + color_attachment[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineDepthStencilStateCreateInfo depth_info = {}; + depth_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + + VkPipelineColorBlendStateCreateInfo blend_info = {}; + blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + blend_info.attachmentCount = 1; + blend_info.pAttachments = color_attachment; + + VkDynamicState dynamic_states[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamic_state = {}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states); + dynamic_state.pDynamicStates = dynamic_states; + + ImGui_ImplVulkan_CreatePipelineLayout(device, allocator); + + VkGraphicsPipelineCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + info.flags = bd->PipelineCreateFlags; + info.stageCount = 2; + info.pStages = stage; + info.pVertexInputState = &vertex_info; + info.pInputAssemblyState = &ia_info; + info.pViewportState = &viewport_info; + info.pRasterizationState = &raster_info; + info.pMultisampleState = &ms_info; + info.pDepthStencilState = &depth_info; + info.pColorBlendState = &blend_info; + info.pDynamicState = &dynamic_state; + info.layout = bd->PipelineLayout; + info.renderPass = renderPass; + info.subpass = subpass; + VkResult err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &info, allocator, pipeline); + check_vk_result(err); +} + +bool ImGui_ImplVulkan_CreateDeviceObjects() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + VkResult err; + + if (!bd->FontSampler) + { + VkSamplerCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + info.magFilter = VK_FILTER_LINEAR; + info.minFilter = VK_FILTER_LINEAR; + info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.minLod = -1000; + info.maxLod = 1000; + info.maxAnisotropy = 1.0f; + err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->FontSampler); + check_vk_result(err); + } + + if (!bd->DescriptorSetLayout) + { + VkSampler sampler[1] = { bd->FontSampler }; + VkDescriptorSetLayoutBinding binding[1] = {}; + binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + binding[0].descriptorCount = 1; + binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + binding[0].pImmutableSamplers = sampler; + VkDescriptorSetLayoutCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.bindingCount = 1; + info.pBindings = binding; + err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &bd->DescriptorSetLayout); + check_vk_result(err); + } + + // Create Descriptor Set: + { + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = v->DescriptorPool; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &bd->DescriptorSetLayout; + err = vkAllocateDescriptorSets(v->Device, &alloc_info, &bd->DescriptorSet); + check_vk_result(err); + } + + if (!bd->PipelineLayout) + { + // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix + VkPushConstantRange push_constants[1] = {}; + push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + push_constants[0].offset = sizeof(float) * 0; + push_constants[0].size = sizeof(float) * 4; + VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; + VkPipelineLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_info.setLayoutCount = 1; + layout_info.pSetLayouts = set_layout; + layout_info.pushConstantRangeCount = 1; + layout_info.pPushConstantRanges = push_constants; + err = vkCreatePipelineLayout(v->Device, &layout_info, v->Allocator, &bd->PipelineLayout); + check_vk_result(err); + } + + ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, bd->RenderPass, v->MSAASamples, &bd->Pipeline, bd->Subpass); + + return true; +} + +void ImGui_ImplVulkan_DestroyFontUploadObjects() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + if (bd->UploadBuffer) + { + vkDestroyBuffer(v->Device, bd->UploadBuffer, v->Allocator); + bd->UploadBuffer = VK_NULL_HANDLE; + } + if (bd->UploadBufferMemory) + { + vkFreeMemory(v->Device, bd->UploadBufferMemory, v->Allocator); + bd->UploadBufferMemory = VK_NULL_HANDLE; + } +} + +void ImGui_ImplVulkan_DestroyDeviceObjects() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); + ImGui_ImplVulkan_DestroyFontUploadObjects(); + + if (bd->ShaderModuleVert) { vkDestroyShaderModule(v->Device, bd->ShaderModuleVert, v->Allocator); bd->ShaderModuleVert = VK_NULL_HANDLE; } + if (bd->ShaderModuleFrag) { vkDestroyShaderModule(v->Device, bd->ShaderModuleFrag, v->Allocator); bd->ShaderModuleFrag = VK_NULL_HANDLE; } + if (bd->FontView) { vkDestroyImageView(v->Device, bd->FontView, v->Allocator); bd->FontView = VK_NULL_HANDLE; } + if (bd->FontImage) { vkDestroyImage(v->Device, bd->FontImage, v->Allocator); bd->FontImage = VK_NULL_HANDLE; } + if (bd->FontMemory) { vkFreeMemory(v->Device, bd->FontMemory, v->Allocator); bd->FontMemory = VK_NULL_HANDLE; } + if (bd->FontSampler) { vkDestroySampler(v->Device, bd->FontSampler, v->Allocator); bd->FontSampler = VK_NULL_HANDLE; } + if (bd->DescriptorSetLayout) { vkDestroyDescriptorSetLayout(v->Device, bd->DescriptorSetLayout, v->Allocator); bd->DescriptorSetLayout = VK_NULL_HANDLE; } + if (bd->PipelineLayout) { vkDestroyPipelineLayout(v->Device, bd->PipelineLayout, v->Allocator); bd->PipelineLayout = VK_NULL_HANDLE; } + if (bd->Pipeline) { vkDestroyPipeline(v->Device, bd->Pipeline, v->Allocator); bd->Pipeline = VK_NULL_HANDLE; } +} + +bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data) +{ + // Load function pointers + // You can use the default Vulkan loader using: + // ImGui_ImplVulkan_LoadFunctions([](const char* function_name, void*) { return vkGetInstanceProcAddr(your_vk_isntance, function_name); }); + // But this would be equivalent to not setting VK_NO_PROTOTYPES. +#if defined(VK_NO_PROTOTYPES) && !defined(VOLK_HEADER_VERSION) +#define IMGUI_VULKAN_FUNC_LOAD(func) \ + func = reinterpret_cast(loader_func(#func, user_data)); \ + if (func == NULL) \ + return false; + IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_LOAD) +#undef IMGUI_VULKAN_FUNC_LOAD +#else + IM_UNUSED(loader_func); + IM_UNUSED(user_data); +#endif + g_FunctionsLoaded = true; + return true; +} + +bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass) +{ + IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); + + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplVulkan_Data* bd = IM_NEW(ImGui_ImplVulkan_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_vulkan"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + + IM_ASSERT(info->Instance != VK_NULL_HANDLE); + IM_ASSERT(info->PhysicalDevice != VK_NULL_HANDLE); + IM_ASSERT(info->Device != VK_NULL_HANDLE); + IM_ASSERT(info->Queue != VK_NULL_HANDLE); + IM_ASSERT(info->DescriptorPool != VK_NULL_HANDLE); + IM_ASSERT(info->MinImageCount >= 2); + IM_ASSERT(info->ImageCount >= info->MinImageCount); + IM_ASSERT(render_pass != VK_NULL_HANDLE); + + bd->VulkanInitInfo = *info; + bd->RenderPass = render_pass; + bd->Subpass = info->Subpass; + + ImGui_ImplVulkan_CreateDeviceObjects(); + + return true; +} + +void ImGui_ImplVulkan_Shutdown() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplVulkan_DestroyDeviceObjects(); + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); +} + +void ImGui_ImplVulkan_NewFrame() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplVulkan_Init()?"); + IM_UNUSED(bd); +} + +void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + IM_ASSERT(min_image_count >= 2); + if (bd->VulkanInitInfo.MinImageCount == min_image_count) + return; + + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + VkResult err = vkDeviceWaitIdle(v->Device); + check_vk_result(err); + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); + bd->VulkanInitInfo.MinImageCount = min_image_count; +} + + +//------------------------------------------------------------------------- +// Internal / Miscellaneous Vulkan Helpers +// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own app.) +//------------------------------------------------------------------------- +// You probably do NOT need to use or care about those functions. +// Those functions only exist because: +// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. +// 2) the upcoming multi-viewport feature will need them internally. +// Generally we avoid exposing any kind of superfluous high-level helpers in the backends, +// but it is too much code to duplicate everywhere so we exceptionally expose them. +// +// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). +// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work. +// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions) +//------------------------------------------------------------------------- + +VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space) +{ + IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); + IM_ASSERT(request_formats != NULL); + IM_ASSERT(request_formats_count > 0); + + // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation + // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format + // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40, + // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used. + uint32_t avail_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, NULL); + ImVector avail_format; + avail_format.resize((int)avail_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, avail_format.Data); + + // First check if only one format, VK_FORMAT_UNDEFINED, is available, which would imply that any format is available + if (avail_count == 1) + { + if (avail_format[0].format == VK_FORMAT_UNDEFINED) + { + VkSurfaceFormatKHR ret; + ret.format = request_formats[0]; + ret.colorSpace = request_color_space; + return ret; + } + else + { + // No point in searching another format + return avail_format[0]; + } + } + else + { + // Request several formats, the first found will be used + for (int request_i = 0; request_i < request_formats_count; request_i++) + for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) + if (avail_format[avail_i].format == request_formats[request_i] && avail_format[avail_i].colorSpace == request_color_space) + return avail_format[avail_i]; + + // If none of the requested image formats could be found, use the first available + return avail_format[0]; + } +} + +VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count) +{ + IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); + IM_ASSERT(request_modes != NULL); + IM_ASSERT(request_modes_count > 0); + + // Request a certain mode and confirm that it is available. If not use VK_PRESENT_MODE_FIFO_KHR which is mandatory + uint32_t avail_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, NULL); + ImVector avail_modes; + avail_modes.resize((int)avail_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, avail_modes.Data); + //for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) + // printf("[vulkan] avail_modes[%d] = %d\n", avail_i, avail_modes[avail_i]); + + for (int request_i = 0; request_i < request_modes_count; request_i++) + for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) + if (request_modes[request_i] == avail_modes[avail_i]) + return request_modes[request_i]; + + return VK_PRESENT_MODE_FIFO_KHR; // Always available +} + +void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator) +{ + IM_ASSERT(physical_device != VK_NULL_HANDLE && device != VK_NULL_HANDLE); + (void)physical_device; + (void)allocator; + + // Create Command Buffers + VkResult err; + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; + ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; + { + VkCommandPoolCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + info.queueFamilyIndex = queue_family; + err = vkCreateCommandPool(device, &info, allocator, &fd->CommandPool); + check_vk_result(err); + } + { + VkCommandBufferAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + info.commandPool = fd->CommandPool; + info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + info.commandBufferCount = 1; + err = vkAllocateCommandBuffers(device, &info, &fd->CommandBuffer); + check_vk_result(err); + } + { + VkFenceCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + err = vkCreateFence(device, &info, allocator, &fd->Fence); + check_vk_result(err); + } + { + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + err = vkCreateSemaphore(device, &info, allocator, &fsd->ImageAcquiredSemaphore); + check_vk_result(err); + err = vkCreateSemaphore(device, &info, allocator, &fsd->RenderCompleteSemaphore); + check_vk_result(err); + } + } +} + +int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode) +{ + if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) + return 3; + if (present_mode == VK_PRESENT_MODE_FIFO_KHR || present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) + return 2; + if (present_mode == VK_PRESENT_MODE_IMMEDIATE_KHR) + return 1; + IM_ASSERT(0); + return 1; +} + +// Also destroy old swap chain and in-flight frames data, if any. +void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count) +{ + VkResult err; + VkSwapchainKHR old_swapchain = wd->Swapchain; + wd->Swapchain = VK_NULL_HANDLE; + err = vkDeviceWaitIdle(device); + check_vk_result(err); + + // We don't use ImGui_ImplVulkanH_DestroyWindow() because we want to preserve the old swapchain to create the new one. + // Destroy old Framebuffer + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); + } + IM_FREE(wd->Frames); + IM_FREE(wd->FrameSemaphores); + wd->Frames = NULL; + wd->FrameSemaphores = NULL; + wd->ImageCount = 0; + if (wd->RenderPass) + vkDestroyRenderPass(device, wd->RenderPass, allocator); + if (wd->Pipeline) + vkDestroyPipeline(device, wd->Pipeline, allocator); + + // If min image count was not specified, request different count of images dependent on selected present mode + if (min_image_count == 0) + min_image_count = ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(wd->PresentMode); + + // Create Swapchain + { + VkSwapchainCreateInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + info.surface = wd->Surface; + info.minImageCount = min_image_count; + info.imageFormat = wd->SurfaceFormat.format; + info.imageColorSpace = wd->SurfaceFormat.colorSpace; + info.imageArrayLayers = 1; + info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; // Assume that graphics family == present family + info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + info.presentMode = wd->PresentMode; + info.clipped = VK_TRUE; + info.oldSwapchain = old_swapchain; + VkSurfaceCapabilitiesKHR cap; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, wd->Surface, &cap); + check_vk_result(err); + if (info.minImageCount < cap.minImageCount) + info.minImageCount = cap.minImageCount; + else if (cap.maxImageCount != 0 && info.minImageCount > cap.maxImageCount) + info.minImageCount = cap.maxImageCount; + + if (cap.currentExtent.width == 0xffffffff) + { + info.imageExtent.width = wd->Width = w; + info.imageExtent.height = wd->Height = h; + } + else + { + info.imageExtent.width = wd->Width = cap.currentExtent.width; + info.imageExtent.height = wd->Height = cap.currentExtent.height; + } + err = vkCreateSwapchainKHR(device, &info, allocator, &wd->Swapchain); + check_vk_result(err); + err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, NULL); + check_vk_result(err); + VkImage backbuffers[16] = {}; + IM_ASSERT(wd->ImageCount >= min_image_count); + IM_ASSERT(wd->ImageCount < IM_ARRAYSIZE(backbuffers)); + err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers); + check_vk_result(err); + + IM_ASSERT(wd->Frames == NULL); + wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount); + wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->ImageCount); + memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount); + memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->ImageCount); + for (uint32_t i = 0; i < wd->ImageCount; i++) + wd->Frames[i].Backbuffer = backbuffers[i]; + } + if (old_swapchain) + vkDestroySwapchainKHR(device, old_swapchain, allocator); + + // Create the Render Pass + { + VkAttachmentDescription attachment = {}; + attachment.format = wd->SurfaceFormat.format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = wd->ClearEnable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkAttachmentReference color_attachment = {}; + color_attachment.attachment = 0; + color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment; + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkRenderPassCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + info.attachmentCount = 1; + info.pAttachments = &attachment; + info.subpassCount = 1; + info.pSubpasses = &subpass; + info.dependencyCount = 1; + info.pDependencies = &dependency; + err = vkCreateRenderPass(device, &info, allocator, &wd->RenderPass); + check_vk_result(err); + + // We do not create a pipeline by default as this is also used by examples' main.cpp, + // but secondary viewport in multi-viewport mode may want to create one with: + //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, bd->Subpass); + } + + // Create The Image Views + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = wd->SurfaceFormat.format; + info.components.r = VK_COMPONENT_SWIZZLE_R; + info.components.g = VK_COMPONENT_SWIZZLE_G; + info.components.b = VK_COMPONENT_SWIZZLE_B; + info.components.a = VK_COMPONENT_SWIZZLE_A; + VkImageSubresourceRange image_range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + info.subresourceRange = image_range; + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; + info.image = fd->Backbuffer; + err = vkCreateImageView(device, &info, allocator, &fd->BackbufferView); + check_vk_result(err); + } + } + + // Create Framebuffer + { + VkImageView attachment[1]; + VkFramebufferCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + info.renderPass = wd->RenderPass; + info.attachmentCount = 1; + info.pAttachments = attachment; + info.width = wd->Width; + info.height = wd->Height; + info.layers = 1; + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; + attachment[0] = fd->BackbufferView; + err = vkCreateFramebuffer(device, &info, allocator, &fd->Framebuffer); + check_vk_result(err); + } + } +} + +// Create or resize window +void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int width, int height, uint32_t min_image_count) +{ + IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); + (void)instance; + ImGui_ImplVulkanH_CreateWindowSwapChain(physical_device, device, wd, allocator, width, height, min_image_count); + ImGui_ImplVulkanH_CreateWindowCommandBuffers(physical_device, device, wd, queue_family, allocator); +} + +void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator) +{ + vkDeviceWaitIdle(device); // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals) + //vkQueueWaitIdle(bd->Queue); + + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); + } + IM_FREE(wd->Frames); + IM_FREE(wd->FrameSemaphores); + wd->Frames = NULL; + wd->FrameSemaphores = NULL; + vkDestroyPipeline(device, wd->Pipeline, allocator); + vkDestroyRenderPass(device, wd->RenderPass, allocator); + vkDestroySwapchainKHR(device, wd->Swapchain, allocator); + vkDestroySurfaceKHR(instance, wd->Surface, allocator); + + *wd = ImGui_ImplVulkanH_Window(); +} + +void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator) +{ + vkDestroyFence(device, fd->Fence, allocator); + vkFreeCommandBuffers(device, fd->CommandPool, 1, &fd->CommandBuffer); + vkDestroyCommandPool(device, fd->CommandPool, allocator); + fd->Fence = VK_NULL_HANDLE; + fd->CommandBuffer = VK_NULL_HANDLE; + fd->CommandPool = VK_NULL_HANDLE; + + vkDestroyImageView(device, fd->BackbufferView, allocator); + vkDestroyFramebuffer(device, fd->Framebuffer, allocator); +} + +void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator) +{ + vkDestroySemaphore(device, fsd->ImageAcquiredSemaphore, allocator); + vkDestroySemaphore(device, fsd->RenderCompleteSemaphore, allocator); + fsd->ImageAcquiredSemaphore = fsd->RenderCompleteSemaphore = VK_NULL_HANDLE; +} + +void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) +{ + if (buffers->VertexBuffer) { vkDestroyBuffer(device, buffers->VertexBuffer, allocator); buffers->VertexBuffer = VK_NULL_HANDLE; } + if (buffers->VertexBufferMemory) { vkFreeMemory(device, buffers->VertexBufferMemory, allocator); buffers->VertexBufferMemory = VK_NULL_HANDLE; } + if (buffers->IndexBuffer) { vkDestroyBuffer(device, buffers->IndexBuffer, allocator); buffers->IndexBuffer = VK_NULL_HANDLE; } + if (buffers->IndexBufferMemory) { vkFreeMemory(device, buffers->IndexBufferMemory, allocator); buffers->IndexBufferMemory = VK_NULL_HANDLE; } + buffers->VertexBufferSize = 0; + buffers->IndexBufferSize = 0; +} + +void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator) +{ + for (uint32_t n = 0; n < buffers->Count; n++) + ImGui_ImplVulkanH_DestroyFrameRenderBuffers(device, &buffers->FrameRenderBuffers[n], allocator); + IM_FREE(buffers->FrameRenderBuffers); + buffers->FrameRenderBuffers = NULL; + buffers->Index = 0; + buffers->Count = 0; +} \ No newline at end of file diff --git a/src/imgui/imgui_impl_vulkan.h b/src/imgui/imgui_impl_vulkan.h new file mode 100644 index 0000000..cbf025b --- /dev/null +++ b/src/imgui/imgui_impl_vulkan.h @@ -0,0 +1,148 @@ +// dear imgui: Renderer Backend for Vulkan +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. +// Missing features: +// [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this backend! See https://github.com/ocornut/imgui/pull/914 + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. +// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ + +// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. +// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. +// You will use those if you want to use this rendering backend in your engine/app. +// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by +// the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. +// Read comments in imgui_impl_vulkan.h. + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +// [Configuration] in order to use a custom Vulkan function loader: +// (1) You'll need to disable default Vulkan function prototypes. +// We provide a '#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES' convenience configuration flag. +// In order to make sure this is visible from the imgui_impl_vulkan.cpp compilation unit: +// - Add '#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES' in your imconfig.h file +// - Or as a compilation flag in your build system +// - Or uncomment here (not recommended because you'd be modifying imgui sources!) +// - Do not simply add it in a .cpp file! +// (2) Call ImGui_ImplVulkan_LoadFunctions() before ImGui_ImplVulkan_Init() with your custom function. +// If you have no idea what this is, leave it alone! +//#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES + +// Vulkan includes +#if defined(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) && !defined(VK_NO_PROTOTYPES) +#define VK_NO_PROTOTYPES +#endif +#include "volk/volk.h" + +// Initialization data, for ImGui_ImplVulkan_Init() +// [Please zero-clear before use!] +struct ImGui_ImplVulkan_InitInfo +{ + VkInstance Instance; + VkPhysicalDevice PhysicalDevice; + VkDevice Device; + uint32_t QueueFamily; + VkQueue Queue; + VkPipelineCache PipelineCache; + VkDescriptorPool DescriptorPool; + uint32_t Subpass; + uint32_t MinImageCount; // >= 2 + uint32_t ImageCount; // >= MinImageCount + VkSampleCountFlagBits MSAASamples; // >= VK_SAMPLE_COUNT_1_BIT + const VkAllocationCallbacks* Allocator; + void (*CheckVkResultFn)(VkResult err); +}; + +// Called by user code +IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); +IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE); +IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer); +IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontUploadObjects(); +IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated) + +// Optional: load Vulkan functions with a custom function loader +// This is only useful with IMGUI_IMPL_VULKAN_NO_PROTOTYPES / VK_NO_PROTOTYPES +IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = NULL); + +//------------------------------------------------------------------------- +// Internal / Miscellaneous Vulkan Helpers +// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.) +//------------------------------------------------------------------------- +// You probably do NOT need to use or care about those functions. +// Those functions only exist because: +// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. +// 2) the upcoming multi-viewport feature will need them internally. +// Generally we avoid exposing any kind of superfluous high-level helpers in the backends, +// but it is too much code to duplicate everywhere so we exceptionally expose them. +// +// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). +// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work. +// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions) +//------------------------------------------------------------------------- + +struct ImGui_ImplVulkanH_Frame; +struct ImGui_ImplVulkanH_Window; + +// Helpers +IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wnd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); +IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wnd, const VkAllocationCallbacks* allocator); +IMGUI_IMPL_API VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space); +IMGUI_IMPL_API VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count); +IMGUI_IMPL_API int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode); + +// Helper structure to hold the data needed by one rendering frame +// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) +// [Please zero-clear before use!] +struct ImGui_ImplVulkanH_Frame +{ + VkCommandPool CommandPool; + VkCommandBuffer CommandBuffer; + VkFence Fence; + VkImage Backbuffer; + VkImageView BackbufferView; + VkFramebuffer Framebuffer; +}; + +struct ImGui_ImplVulkanH_FrameSemaphores +{ + VkSemaphore ImageAcquiredSemaphore; + VkSemaphore RenderCompleteSemaphore; +}; + +// Helper structure to hold the data needed by one rendering context into one OS window +// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) +struct ImGui_ImplVulkanH_Window +{ + int Width; + int Height; + VkSwapchainKHR Swapchain; + VkSurfaceKHR Surface; + VkSurfaceFormatKHR SurfaceFormat; + VkPresentModeKHR PresentMode; + VkRenderPass RenderPass; + VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo + bool ClearEnable; + VkClearValue ClearValue; + uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount) + uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count) + uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data) + ImGui_ImplVulkanH_Frame* Frames; + ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores; + + ImGui_ImplVulkanH_Window() + { + memset(this, 0, sizeof(*this)); + PresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; + ClearEnable = true; + } +}; diff --git a/src/renderer.cpp b/src/renderer.cpp index fddd2ce..91f4c44 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -4,10 +4,12 @@ #include #include +#include "imgui/imgui.h" + [[maybe_unused]] static glm::mat4 GenPerspectiveMatrix(float vertical_fov_radians, float aspect_ratio, float znear, float zfar) { - float g = 1.0f / tan(vertical_fov_radians * 0.5); + float g = 1.0f / tanf(vertical_fov_radians * 0.5f); float k1 = zfar / (zfar - znear); float k2 = -(zfar * znear) / (znear - zfar); glm::mat4 m{1.0f}; @@ -69,6 +71,8 @@ Renderer::Renderer(const char* app_name, const char* app_version, binding0.stage_flags = gfx::ShaderStageFlags::kFragment; } material_set_layout = device_->CreateDescriptorSetLayout(materialSetBindings); + + device_->SetupImguiBackend(); }; Renderer::~Renderer() { @@ -121,6 +125,8 @@ void Renderer::Render(const RenderList& static_list, DrawRenderList(draw_buffer, dynamic_list); } + device_->CmdRenderImguiDrawData(draw_buffer, ImGui::GetDrawData()); + device_->FinishRender(draw_buffer); } diff --git a/src/scene.cpp b/src/scene.cpp index adfa74d..a59d3f5 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -36,7 +36,8 @@ Scene::Scene(Application* app) : app_(app) { Scene::~Scene() {} Entity Scene::CreateEntity(const std::string& tag, Entity parent, - const glm::vec3& pos) { + const glm::vec3& pos, const glm::quat& rot, + const glm::vec3& scl) { Entity id = next_entity_id_++; signatures_.emplace(id, std::bitset{}); @@ -44,8 +45,8 @@ Entity Scene::CreateEntity(const std::string& tag, Entity parent, auto t = AddComponent(id); t->position = pos; - t->rotation = {}; - t->scale = {1.0f, 1.0f, 1.0f}; + t->rotation = rot; + t->scale = scl; t->tag = tag; t->parent = parent; diff --git a/src/window.cpp b/src/window.cpp index 74eb7b1..707928b 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3,6 +3,8 @@ #include #include +#include + static const uint64_t BILLION = 1000000000; namespace engine { @@ -109,63 +111,85 @@ namespace engine { void Window::OnKeyEvent(SDL_KeyboardEvent& e) { - bool keyWasDown = keyboard_.keys[e.keysym.scancode]; - bool keyIsDown = (e.state == SDL_PRESSED); - keyboard_.keys[e.keysym.scancode] = keyIsDown; - if (keyIsDown != keyWasDown) { // (if key was pressed or released) - keyboard_.deltas[e.keysym.scancode] = keyIsDown ? ButtonDelta::kPressed : ButtonDelta::kReleased; + const ImGuiIO& io = ImGui::GetIO(); + if (io.WantCaptureKeyboard) { + keyboard_.deltas.fill(ButtonDelta::kSame); + } + else { + bool keyWasDown = keyboard_.keys[e.keysym.scancode]; + bool keyIsDown = (e.state == SDL_PRESSED); + keyboard_.keys[e.keysym.scancode] = keyIsDown; + if (keyIsDown != keyWasDown) { // (if key was pressed or released) + keyboard_.deltas[e.keysym.scancode] = keyIsDown ? ButtonDelta::kPressed : ButtonDelta::kReleased; + } } } void Window::OnMouseButtonEvent(SDL_MouseButtonEvent& e) { - enum inputs::MouseButton button = inputs::MouseButton::M_INVALID; - switch (e.button) { - case SDL_BUTTON_LEFT: - button = inputs::MouseButton::M_LEFT; - break; - case SDL_BUTTON_MIDDLE: - button = inputs::MouseButton::M_MIDDLE; - break; - case SDL_BUTTON_RIGHT: - button = inputs::MouseButton::M_RIGHT; - break; - case SDL_BUTTON_X1: - button = inputs::MouseButton::M_X1; - break; - case SDL_BUTTON_X2: - button = inputs::MouseButton::M_X2; - break; + const ImGuiIO& io = ImGui::GetIO(); + if (io.WantCaptureMouse) { + mouse_.deltas.fill(ButtonDelta::kSame); } - int buttonIndex = static_cast(button); - bool buttonWasDown = mouse_.buttons.at(buttonIndex); - bool buttonIsDown = (e.state == SDL_PRESSED); - mouse_.buttons.at(buttonIndex) = buttonIsDown; - if (buttonIsDown != buttonWasDown) { // (if button was pressed or released) - // only sets delta if it hasn't already been set this frame (to detect very fast presses) - if (mouse_.deltas[buttonIndex] == ButtonDelta::kSame) { - mouse_.deltas[buttonIndex] = buttonIsDown ? ButtonDelta::kPressed : ButtonDelta::kReleased; + else { + enum inputs::MouseButton button = inputs::MouseButton::M_INVALID; + switch (e.button) { + case SDL_BUTTON_LEFT: + button = inputs::MouseButton::M_LEFT; + break; + case SDL_BUTTON_MIDDLE: + button = inputs::MouseButton::M_MIDDLE; + break; + case SDL_BUTTON_RIGHT: + button = inputs::MouseButton::M_RIGHT; + break; + case SDL_BUTTON_X1: + button = inputs::MouseButton::M_X1; + break; + case SDL_BUTTON_X2: + button = inputs::MouseButton::M_X2; + break; + } + int buttonIndex = static_cast(button); + bool buttonWasDown = mouse_.buttons.at(buttonIndex); + bool buttonIsDown = (e.state == SDL_PRESSED); + mouse_.buttons.at(buttonIndex) = buttonIsDown; + if (buttonIsDown != buttonWasDown) { // (if button was pressed or released) + // only sets delta if it hasn't already been set this frame (to detect very fast presses) + if (mouse_.deltas[buttonIndex] == ButtonDelta::kSame) { + mouse_.deltas[buttonIndex] = buttonIsDown ? ButtonDelta::kPressed : ButtonDelta::kReleased; + } } } } void Window::OnMouseMotionEvent(SDL_MouseMotionEvent& e) { - mouse_.x = e.x; - mouse_.y = e.y; - mouse_.dx = e.xrel; - mouse_.dy = e.yrel; + const ImGuiIO& io = ImGui::GetIO(); + if (io.WantCaptureMouse) { + mouse_.dx = 0.0f; + mouse_.dy = 0.0f; + } + else { + mouse_.x = e.x; + mouse_.y = e.y; + mouse_.dx = e.xrel; + mouse_.dy = e.yrel; + } } void Window::OnMouseWheelEvent(SDL_MouseWheelEvent& e) { - if (e.direction == SDL_MOUSEWHEEL_NORMAL) { - mouse_.xscroll = e.preciseX; - mouse_.yscroll = e.preciseY; - } - else { // flipped - mouse_.xscroll = -e.preciseX; - mouse_.yscroll = -e.preciseY; + const ImGuiIO& io = ImGui::GetIO(); + if (!io.WantCaptureMouse) { + if (e.direction == SDL_MOUSEWHEEL_NORMAL) { + mouse_.xscroll = e.preciseX; + mouse_.yscroll = e.preciseY; + } + else { // flipped + mouse_.xscroll = -e.preciseX; + mouse_.yscroll = -e.preciseY; + } } } @@ -194,6 +218,7 @@ namespace engine { // loop through all available events SDL_Event e; while (SDL_PollEvent(&e)) { + ImGui_ImplSDL2_ProcessEvent(&e); switch (e.type) { case SDL_QUIT: diff --git a/test/res/models/AxesTexture.png b/test/res/models/AxesTexture.png new file mode 100644 index 0000000000000000000000000000000000000000..ead3a8aac5e9f07898a3e93f5d256b0ea191dbba GIT binary patch literal 40120 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