Show pageOld revisionsBacklinksBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== NervLand DevLog #25: Introducing support for IMGUI ====== {{tag>dev cpp webgpu nervland devlog imgui gui}} Hello hello! In our [[blog:2023:0726_nvl_devlog24_more_text_rendering_features|previous devlog article]] we introduced some "additional features" on the text rendering system I'm building in NervLand. One of the main interest when introducing such support for text rendering, if of course to get ready to builf some user interfaces, and eventually, I think I will get to this. But for this new episode, I'm rather going to try to take a shortcut for once, and figure out how to integrate IMGUI in our engine 😎! From what I've read already, imgui should provide us with "backends" for both SDL2 and webgpu, which are exactly what we need, so, what go wrong really ?? 🤣🤣🤣 ====== ====== Youtube video for this article available at: ;#; {{ youtube>tCV42V6O2KU?large }} ;#; ===== ✅ Setting up imgui source repo ===== Hmmm... first thing already: I have already forked the imgui repository on github (cf. https://github.com/ocornut/imgui), but I forked the **master** branch, and now, I can't find a to change to the **docking** branch 😲, come on github, really ?! And in the end I could find a way to have 2 different branches forked on github directly, so the easiest solution for me here is to delete my existing fork. For that, you need to go on the forked repository **Settings** tab: {{ blog:2023:034_github_settings.png }} Then you navigate down to the **Danger zone**: {{ blog:2023:035_github_delete_repo.png }} And now, I can create a new fork, using the branch **docking** as source 😉! But this time I'm making sure that I'm not only forking the master branch by unchecking the checkbox below (⚠️ it is checked by default): {{ blog:2023:036_github_fork_repo.png }} Now checking out the repo ans switching to the docking branch: <sxh bash; highlight: []>git clone git@github.com:roche-emmanuel/imgui.git cd imgui git fetch git branch -r git checkout -t origin/docking</sxh> ===== ✅ Integrating imgui source files ===== Now I need to integrate the imgui sources into my own project: {{ blog:2023:037_nvl_adding_imgui_files.png }} Then I just had to extend my list of input files and add the include directory for SDL2, and the build of the nvGPU module will go just fine 👍! ===== ✅ Initial imgui test 🤞 ===== Using this file as template: https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_wgpu/main.cpp I now everything setup properly and added a dedicated render pass to process the imgui data: <sxh cpp; highlight: []> // Add a dedicated render pass to display the imgui data: auto& rpass2 = eng->create_render_pass(width, height, {true, 0}); rpass2.set_encode_func([](RenderPassEncoder& pass) { ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass.Get()); });</sxh> /*//*/ but unfortunately, this doesn't work 😭... some validation error from wgpu... let's see... Hmmm, I have this error message: <code>2023-07-27 13:30:39.115464 [ERROR] Dawn: Validation error: Attachment state of [RenderPipeline] is not compatible with [RenderPassEncoder]. [RenderPassEncoder] expects an attachment state of { colorFormats: [TextureFormat::BGRA8Unorm], depthStencilFormat: TextureFormat::Depth24PlusStencil8, sampleCount: 1 }. [RenderPipeline] has an attachment state of { colorFormats: [TextureFormat::RGBA8Unorm], sampleCount: 1 }.</code> So it could simply be that I need to pass the depth stencil format too when initializing imgui. Trying that with: <sxh cpp; highlight: []> ImGui_ImplWGPU_Init(eng->get_device(), 3, WGPUTextureFormat_RGBA8Unorm, WGPUTextureFormat_Depth24PlusStencil8);</sxh> Nope 😭😭, still not good enough: <code>2023-07-27 13:35:51.843629 [ERROR] Dawn: Validation error: Attachment state of [RenderPipeline] is not compatible with [RenderPassEncoder]. [RenderPassEncoder] expects an attachment state of { colorFormats: [TextureFormat::BGRA8Unorm], depthStencilFormat: TextureFormat::Depth24PlusStencil8, sampleCount: 1 }. [RenderPipeline] has an attachment state of { colorFormats: [TextureFormat::RGBA8Unorm], depthStencilFormat: TextureFormat::Depth24PlusStencil8, sampleCount: 1 }.</code> Ohhh, that's the color attachment format in fact, I'm using BRGA myself but configured imgui to use RGBA, stupid me. Fixing it... **Cooll!** Now it's starting to work a little bit more, here is what I see: {{ blog:2023:038_nvl_first_imgui_display.png }} Mouse movements are detected properly since the hover color is changed when I move on different elements, but I can't click any widget, or drag anything. So could be the mouse button handling is not working yet ? Also it's strange that the window seems to be pushed on the right, and also strange that I cannot see my background cubemap 🤔. Let's start investigating. Oh, I see, this is what I was missing: <sxh cpp; highlight: []>// 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)</sxh> /*//*/ I have thus introduced support to add custom global event handlers in the SDLWindowManager: <sxh cpp; highlight: []> auto* wman = (SDLWindowManager*)SDLWindowManager::instance(); wman->add_event_handler([](SDL_Event& evt) { ImGui_ImplSDL2_ProcessEvent(&evt); // ImGuiIO& io = ImGui::GetIO(); return false; });</sxh> And now the display of the imgui demo window is OK! {{ blog:2023:039_nvl_working_imgui_display.png }} But I still have this black background issue... or could this just be because of my render pass setup 🤔? Checking this... And sure enough, this was it: my second render pass was configured to clear its target, fixing it: <sxh cpp; highlight: []> auto& rpass2 = eng->create_render_pass( width, height, {.with_depth = true, .swapchain_idx = 0, .clear_color = false}); rpass2.set_encode_func([](RenderPassEncoder& pass) { ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass.Get()); });</sxh> And now I have this nice display result: {{ blog:2023:040_nvl_imgui_display_noclear.png }} {{ blog:2023:041_nvl_imgui_display_noclear2.png }} => That is so amazing 🤩! Yet, there is a final change I need to make: as mentioned in the C++ comment above I should prevent the handling my the mouse events if IMGUI is currently "using" the mouse, otherwise I'm interacting with the scene when moving the GUI itself: <sxh cpp; highlight: []> auto* wman = (SDLWindowManager*)SDLWindowManager::instance(); wman->add_event_handler([](SDL_Event& evt) { ImGui_ImplSDL2_ProcessEvent(&evt); ImGuiIO& io = ImGui::GetIO(); return io.WantCaptureMouse || io.WantCaptureKeyboard; });</sxh> => Allright, with that small change I can now drag/interact with the GUI without intefering on the scene itself. ===== ✅ Trying to enable docking ===== Just activated the support for docking/multi-viewports: <sxh cpp; highlight: []> ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport // / Platform Windows // io.ConfigViewportsNoAutoMerge = true; // io.ConfigViewportsNoTaskBarIcon = true;</sxh> And docking seems to work, even if that's not fully what I was expecting (I was expecting to be able to dock at the border of the actual SDL window, but that is not how it works ;-)): {{ blog:2023:005_imgui_docking.gif }} Unfortunately, the "multi-viewports" part doesn't seem to work 🤔... Not real idea why, maybe because of the webgpu backend not really supporting that yet ? I could try to investigate this further one day, but for now I think this is not a priority anyway. Ohhh, but in fact, I just noticed in the SDL2/OpenGL3 example that there is another dedicated code section for the multi-viewports that I don't have yet: <sxh cpp; highlight: []> // Update and Render additional Platform Windows // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. // For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow(); SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext(); ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); SDL_GL_MakeCurrent(backup_current_window, backup_current_context); }</sxh> => Let's try to add something like that: <sxh cpp; highlight: []> // Update and Render additional Platform Windows // (Platform functions may change the current OpenGL context, so we // save/restore it to make it easier to paste this code elsewhere. // For this specific demo app we could also call SDL_GL_MakeCurrent(window, // gl_context) directly) if ((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) { ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); }</sxh> Hmmm, nope, not really changing anything. And I eventually realized that, even if I request this feature, the flag will be removed from the ConfigFlags. And searching in the imgui code I found this section: <sxh cpp; highlight: []> if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports)) { IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) && "Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ applications for reference."); IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?"); IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?"); IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?"); IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?"); IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?"); IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?"); IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?"); IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) && "Platform init didn't setup main viewport."); if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL && "Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!"); } else { // Disable feature, our backends do not support it g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable; }</sxh> So yeah, pretty sure that WebGPU doesn't support multiviewport yet (as this is probably implemented with on rendering in browser with a "single window" in mind 😉). As I said: never mind for now. /* On the backend we would need **ImGuiBackendFlags_PlatformHasViewports** and **ImGuiBackendFlags_RendererHasViewports** The flag "ImGuiBackendFlags_PlatformHasViewports" is already set so only ImGuiBackendFlags_RendererHasViewports is missing. */ blog/2023/0729_nvl_imgui_integration.txt Last modified: 2023/09/09 21:58by 127.0.0.1