6#include "common/config.h"
10#include <imgui_impl_sdl.h>
17#include <imgui_impl_vulkan.h>
18#include <vulkan/vulkan.h>
19#include <SDL2/SDL_vulkan.h>
33#ifndef EVENGINE_WEBGPU
34void checkVk(VkResult err) {
36 throw eve::Exception(
"ImGui Vulkan error: VkResult = %d",
int(err));
41constexpr float kBaseFontSizePx = 16.f;
43std::vector<const char *> regularFontCandidates() {
45 static const std::string winDir = [] {
46 const char *
dir = getenv(
"WINDIR");
47 return dir ? std::string(
dir) : std::string(
"C:\\Windows");
49 static const std::vector<std::string> paths = {
50 winDir +
"\\Fonts\\segoeui.ttf", winDir +
"\\Fonts\\arial.ttf",
51 winDir +
"\\Fonts\\tahoma.ttf", winDir +
"\\Fonts\\msyh.ttc",
53#elif defined(__APPLE__)
54 static const std::vector<std::string> paths = {
55 "/System/Library/Fonts/Helvetica.ttc",
56 "/System/Library/Fonts/SFNS.ttf",
57 "/Library/Fonts/Arial.ttf",
59#elif defined(EVENGINE_ANDROID)
60 static const std::vector<std::string> paths = {
61 "/system/fonts/NotoSansCJK-Regular.ttc",
62 "/system/fonts/DroidSansFallback.ttf",
63 "/system/fonts/Roboto-Regular.ttf",
66 static const std::vector<std::string> paths = {
67 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
68 "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
69 "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
70 "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
71 "/usr/share/fonts/truetype/arphic/uming.ttc",
74 std::vector<const char *> out;
75 out.reserve(paths.size());
76 for (
const auto &
p : paths) out.push_back(
p.c_str());
80std::vector<const char *> iconFontCandidates() {
81 static const std::vector<std::string> paths = {
82 "fonts/FontAwesome.ttf",
83 "test/fonts/FontAwesome.ttf",
85 std::vector<const char *> out;
86 out.reserve(paths.size());
87 for (
const auto &
p : paths) out.push_back(
p.c_str());
91bool fileExists(
const char *path) {
92 if (!path || !*path)
return false;
93 FILE *
f = fopen(path,
"rb");
102 return std::make_unique<ImGuiBackend>();
108 if (initialized_)
return true;
109 if (!
window || !gfx)
return false;
110 StartupStage initStage(
"ui: ImGui backend init (first frame)");
114 dpiScale_ = computeDpiScale();
115 uiScale_ = computeInitialScale();
117 IMGUI_CHECKVERSION();
118 ctx_ = ::ImGui::CreateContext();
124 ImGui_ImplSDL2_InitForVulkan(
window);
126#ifdef EVENGINE_WEBGPU
128 if (!wgg)
return false;
132 fontsUploaded_ =
true;
135 if (!vkg)
return false;
136 if (!vkg->getSwapchainRenderPass())
return false;
139 VkDescriptorPoolSize poolSizes[] = {
140 {VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
141 {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
142 {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
143 {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
144 {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
145 {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
146 {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
147 {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
148 {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
149 {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
150 {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000},
152 VkDescriptorPoolCreateInfo poolInfo{};
153 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
154 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
155 poolInfo.maxSets = 1000 * uint32_t(
sizeof(poolSizes) /
sizeof(poolSizes[0]));
156 poolInfo.poolSizeCount = uint32_t(
sizeof(poolSizes) /
sizeof(poolSizes[0]));
157 poolInfo.pPoolSizes = poolSizes;
159 VkDescriptorPool pool = VK_NULL_HANDLE;
160 checkVk(vkCreateDescriptorPool(
static_cast<VkDevice
>(
device.instance), &poolInfo,
nullptr, &pool));
161 imguiDescriptorPool_ = pool;
163 uint32_t imageCount = vkg->getSwapchainImageCount();
164 if (imageCount < 2) imageCount = 2;
166 vkg->ensureUiColorResources();
168 ImGui_ImplVulkan_InitInfo initInfo{};
169 initInfo.Instance =
static_cast<VkInstance
>(vkg->getInstance().instance);
170 initInfo.PhysicalDevice =
static_cast<VkPhysicalDevice
>(
device.physical_device.instance);
171 initInfo.Device =
static_cast<VkDevice
>(
device.instance);
172 initInfo.QueueFamily =
device.get_queue_index(vkb::QueueType::graphics);
173 initInfo.Queue =
static_cast<VkQueue
>(
device.getQueue(vkb::QueueType::graphics));
174 initInfo.PipelineCache = VK_NULL_HANDLE;
175 initInfo.DescriptorPool = pool;
176 initInfo.MinImageCount = imageCount;
177 initInfo.ImageCount = imageCount;
178 initInfo.MSAASamples =
static_cast<VkSampleCountFlagBits
>(vkg->getUiMsaaSamples());
179 initInfo.CheckVkResultFn = [](VkResult err) {
180 if (err != 0) fprintf(stderr,
"ImGui Vulkan: VkResult %d\n",
int(err));
183 ImGui_ImplVulkan_Init(&initInfo,
static_cast<VkRenderPass
>(vkg->getUiMsaaRenderPass()));
186 StartupStage fontStage(
" ui: font load + atlas upload");
189 vkb::executeImmediately(
device.instance, vkg->getUploadPool(),
190 device.getQueue(vkb::QueueType::graphics), [&](vk::CommandBuffer cb) {
191 ImGui_ImplVulkan_CreateFontsTexture(static_cast<VkCommandBuffer>(cb));
193 ImGui_ImplVulkan_DestroyFontUploadObjects();
194 fontsUploaded_ =
true;
208void ImGuiBackend::windowDestroyedThunk(
void *userdata) {
214 if (!initialized_)
return;
222#ifdef EVENGINE_WEBGPU
224 ImGui_ImplSDL2_Shutdown();
225 ImGui::DestroyContext();
229 vkDeviceWaitIdle(
static_cast<VkDevice
>(vkg->getDevice().instance));
235 ::ImGuiContext *prev = ::ImGui::GetCurrentContext();
236 ::ImGuiContext *mine = ctx_;
237 ::ImGui::SetCurrentContext(mine);
238 ::ImGui_ImplVulkan_Shutdown();
239 ::ImGui_ImplSDL2_Shutdown();
240 ::ImGui::DestroyContext(mine);
244 if (prev && prev != mine) ::ImGui::SetCurrentContext(prev);
245 else ::ImGui::SetCurrentContext(
nullptr);
247 if (imguiDescriptorPool_ && vkg) {
248 vkDestroyDescriptorPool(
static_cast<VkDevice
>(vkg->getDevice().instance),
249 static_cast<VkDescriptorPool
>(imguiDescriptorPool_),
nullptr);
250 imguiDescriptorPool_ =
nullptr;
252 if (imguiTexturePool_ && vkg) {
253 vkDestroyDescriptorPool(
static_cast<VkDevice
>(vkg->getDevice().instance),
254 static_cast<VkDescriptorPool
>(imguiTexturePool_),
nullptr);
255 imguiTexturePool_ =
nullptr;
257 if (imguiTextureLayout_ && vkg) {
258 vkDestroyDescriptorSetLayout(
static_cast<VkDevice
>(vkg->getDevice().instance),
259 static_cast<VkDescriptorSetLayout
>(imguiTextureLayout_),
261 imguiTextureLayout_ =
nullptr;
266 fontsUploaded_ =
false;
267 initialized_ =
false;
271 if (!initialized_ || !event)
return;
272 ImGui_ImplSDL2_ProcessEvent(event);
276 if (!initialized_ || !window_)
return;
281#ifdef EVENGINE_WEBGPU
284 ImGui_ImplVulkan_NewFrame();
286 ImGui_ImplSDL2_NewFrame(window_);
291void ImGuiBackend::applyScale(
float scale) {
292 if (!initialized_)
return;
294 const bool changed =
scale != uiScale_;
299 if (changed) rebuildFonts();
303 if (!initialized_)
return;
307float ImGuiBackend::computeInitialScale()
const {
308#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS)
310 if (SDL_GetDisplayDPI(0, &ddpi,
nullptr,
nullptr) != 0 || ddpi < 1.f) ddpi = 320.f;
311 float s = ddpi / 160.f;
312 return std::clamp(
s, 1.75f, 3.25f);
323float ImGuiBackend::computeDpiScale()
const {
324 int logicalW = 0, logicalH = 0, pixelW = 0, pixelH = 0;
325 SDL_GetWindowSize(window_, &logicalW, &logicalH);
326#ifdef EVENGINE_WEBGPU
328 SDL_GetWindowSize(window_, &pixelW, &pixelH);
330 SDL_Vulkan_GetDrawableSize(window_, &pixelW, &pixelH);
332 if (logicalW > 0 && pixelW > 0) {
333 float s = float(pixelW) / float(logicalW);
334 if (
s > 0.f)
return std::clamp(
s, 1.f, 4.f);
339void ImGuiBackend::loadFonts() {
340 ImGuiIO &io = ImGui::GetIO();
341 ImFontAtlas *atlas = io.Fonts;
347 const float sizePx = kBaseFontSizePx * dpiScale_;
355 ImFontGlyphRangesBuilder rangeBuilder;
356 rangeBuilder.AddRanges(atlas->GetGlyphRangesDefault());
357 rangeBuilder.AddRanges(atlas->GetGlyphRangesChineseFull());
358 rangeBuilder.BuildRanges(&fontRanges_);
359 cfg.GlyphRanges = fontRanges_.Data;
362 for (
const char *path : regularFontCandidates()) {
363 if (!fileExists(path))
continue;
364 if (atlas->AddFontFromFileTTF(path, sizePx, &cfg)) {
369 if (!added) atlas->AddFontDefault(&cfg);
371 ImFontConfig iconCfg{};
372 iconCfg.OversampleH = 2;
373 iconCfg.OversampleV = 2;
374 iconCfg.MergeMode =
true;
375 iconCfg.PixelSnapH =
true;
376 static const ImWchar iconRanges[] = {0xF000, 0xF8FF, 0};
377 for (
const char *path : iconFontCandidates()) {
378 if (!fileExists(path))
continue;
379 if (atlas->AddFontFromFileTTF(path, sizePx, &iconCfg, iconRanges))
break;
383void ImGuiBackend::rebuildFonts() {
384#ifdef EVENGINE_WEBGPU
388 fontsUploaded_ =
true;
393 ImGui_ImplVulkan_DestroyFontUploadObjects();
394 auto &
device = vkg->getDevice();
395 vkb::executeImmediately(
device.instance, vkg->getUploadPool(),
396 device.getQueue(vkb::QueueType::graphics),
397 [&](vk::CommandBuffer cb) {
398 ImGui_ImplVulkan_CreateFontsTexture(
399 static_cast<VkCommandBuffer>(cb));
401 ImGui_ImplVulkan_DestroyFontUploadObjects();
402 fontsUploaded_ =
true;
406uint64_t ImGuiBackend::registerTexture(graphics::Texture *tex) {
407 if (!initialized_ || !tex || !tex->gpuHandle)
return 0;
408 RegisteredTexture reg;
409#ifdef EVENGINE_WEBGPU
411 reg.imId = (ImTextureID)(intptr_t)gt->
view.Get();
412 if (!reg.imId)
return 0;
417 const VkDevice
device =
static_cast<VkDevice
>(vkg->getDevice().instance);
418 if (!imguiTextureLayout_) {
419 VkDescriptorSetLayoutBinding binding{};
421 binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
422 binding.descriptorCount = 1;
423 binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
424 VkDescriptorSetLayoutCreateInfo info{};
425 info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
426 info.bindingCount = 1;
427 info.pBindings = &binding;
428 VkDescriptorSetLayout
layout = VK_NULL_HANDLE;
429 checkVk(vkCreateDescriptorSetLayout(
device, &info,
nullptr, &
layout));
430 imguiTextureLayout_ =
layout;
432 if (!imguiTexturePool_) {
433 VkDescriptorPoolSize size{};
434 size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
435 size.descriptorCount = 512;
436 VkDescriptorPoolCreateInfo info{};
437 info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
438 info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
440 info.poolSizeCount = 1;
441 info.pPoolSizes = &size;
442 VkDescriptorPool pool = VK_NULL_HANDLE;
443 checkVk(vkCreateDescriptorPool(
device, &info,
nullptr, &pool));
444 imguiTexturePool_ = pool;
446 VkDescriptorSet set = VK_NULL_HANDLE;
447 VkDescriptorSetAllocateInfo ai{};
448 ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
449 ai.descriptorPool =
static_cast<VkDescriptorPool
>(imguiTexturePool_);
450 ai.descriptorSetCount = 1;
451 const VkDescriptorSetLayout
layout =
static_cast<VkDescriptorSetLayout
>(imguiTextureLayout_);
453 checkVk(vkAllocateDescriptorSets(
device, &ai, &set));
454 VkDescriptorImageInfo imageInfo{};
455 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
456 imageInfo.imageView =
static_cast<VkImageView
>(gt->imageView());
457 imageInfo.sampler =
static_cast<VkSampler
>(gt->sampler);
458 VkWriteDescriptorSet write{};
459 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
461 write.dstBinding = 0;
462 write.descriptorCount = 1;
463 write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
464 write.pImageInfo = &imageInfo;
465 vkUpdateDescriptorSets(
device, 1, &write, 0,
nullptr);
468 reg.width = tex->getWidth();
469 reg.height = tex->getHeight();
470 const uint64_t key = nextTextureKey_++;
471 textures_[key] = reg;
475void ImGuiBackend::unregisterTexture(uint64_t
id) {
476 auto it = textures_.find(
id);
477 if (it == textures_.end())
return;
478#ifndef EVENGINE_WEBGPU
480 if (vkg && imguiTexturePool_) {
481 VkDescriptorSet set =
static_cast<VkDescriptorSet
>(it->second.imId);
482 vkFreeDescriptorSets(
static_cast<VkDevice
>(vkg->getDevice().instance),
483 static_cast<VkDescriptorPool
>(imguiTexturePool_), 1, &set);
489bool ImGuiBackend::textureSize(uint64_t
id,
int *
w,
int *
h)
const {
490 auto it = textures_.find(
id);
491 if (it == textures_.end())
return false;
492 if (
w) *
w = it->second.width;
493 if (
h) *
h = it->second.height;
497void *ImGuiBackend::textureHandle(uint64_t
id)
const {
498 auto it = textures_.find(
id);
499 return it == textures_.end() ? nullptr :
static_cast<void *
>(it->second.imId);
503 if (!initialized_)
return false;
504 return ImGui::GetIO().WantCaptureMouse;
508 if (!initialized_)
return false;
509 return ImGui::GetIO().WantCaptureKeyboard;
512void ImGuiBackend::renderDrawData(
void *commandBuffer) {
513 if (!initialized_ || !commandBuffer)
return;
516#ifdef EVENGINE_WEBGPU
517 WGPURenderPassEncoder enc = *
static_cast<WGPURenderPassEncoder *
>(commandBuffer);
520 ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(),
521 static_cast<VkCommandBuffer
>(commandBuffer));
525void ImGuiBackend::presentOverlayThunk(
void *userdata,
void *commandBuffer) {
528 self->renderDrawData(commandBuffer);
void addWindowDestroyedCallback(WindowDestroyedCallback cb, void *userdata)
void * getPresentOverlayUser() const
void setPresentOverlay(PresentOverlayFn fn, void *userdata)
vkb::Device & getDevice()
Dear ImGui + SDL input + Vulkan present overlay.
bool wantCaptureKeyboard() const override
bool init(SDL_Window *window, eve::graphics::Graphics *gfx) override
bool wantCaptureMouse() const override
void setScale(float scale) override
Scale fonts + ImGui style metrics (1 = default desktop).
void processEvent(const SDL_Event *event) override
IMGUI_IMPL_API void ImGui_ImplWGPU_NewFrame()
IMGUI_IMPL_API bool ImGui_ImplWGPU_CreateFontsTexture()
IMGUI_IMPL_API void ImGui_ImplWGPU_RenderDrawData(ImDrawData *draw_data, WGPURenderPassEncoder pass_encoder)
IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format)
IMGUI_IMPL_API void ImGui_ImplWGPU_Shutdown()
IMGUI_IMPL_API void ImGui_ImplWGPU_InvalidateDeviceObjects()
void applyThemeToImGui(const Theme &theme)
Push tokens into ImGui style. Metrics are multiplied by uiScale (default: themeUiScale()).
void setThemeDpiScale(float dpiScale)
std::unique_ptr< UIBackend > createImGuiBackend()
Default backend: Dear ImGui + SDL + Vulkan (see ui/imgui/).
void setThemeUiScale(float scale)
Logical (point-space) UI scale. Default 1.0.
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
Texture resources backed by a wgpu texture + view + sampler + bind groups.