## ImGui System: Immediate-Mode UI Integration Manages Dear ImGui lifecycle, event processing, and rendering within the Vulkan engine. Provides DPI-aware font scaling and callback-based UI composition. ### Components - `ImGuiSystem` (src/core/ui/imgui_system.h/.cpp) - Initializes ImGui context with Vulkan backend. - Processes SDL events for ImGui input. - Manages draw callback registration. - Handles DPI scaling and font rebuilding. - `ImGuiPass` (src/render/passes/imgui_pass.h/.cpp) - RenderGraph pass that records ImGui draw commands. - Renders to swapchain image using dynamic rendering. - `engine_ui.cpp` - Built-in debug UI widgets (stats, render graph, texture streaming, etc.). - Example of using the draw callback system. ### ImGuiSystem API **Initialization:** ```cpp void init(EngineContext *context); void cleanup(); ``` **Frame Lifecycle:** ```cpp void begin_frame(); // NewFrame + invoke draw callbacks void end_frame(); // ImGui::Render() ``` **Event Processing:** ```cpp void process_event(const SDL_Event &event); ``` **Draw Callbacks:** ```cpp void add_draw_callback(DrawCallback callback); void clear_draw_callbacks(); // DrawCallback type using DrawCallback = std::function; ``` **Input Capture Queries:** ```cpp bool want_capture_mouse() const; bool want_capture_keyboard() const; ``` **Swapchain Events:** ```cpp void on_swapchain_recreated(); // Update image count after resize ``` ### Usage Examples **Basic Setup (Engine Internal):** ```cpp // In VulkanEngine::init() _imgui_system.init(&_context); // Register debug UI callback _imgui_system.add_draw_callback([this]() { vk_engine_draw_debug_ui(this); }); ``` **Event Processing:** ```cpp // In event loop for (const SDL_Event& event : events) { _imgui_system.process_event(event); } ``` **Frame Integration:** ```cpp // Start of frame (after input processing) _imgui_system.begin_frame(); // ... game update, scene rendering ... // End of frame (before RenderGraph execution) _imgui_system.end_frame(); ``` **Custom UI Callback:** ```cpp void Game::init() { engine.imgui_system().add_draw_callback([this]() { draw_game_ui(); }); } void Game::draw_game_ui() { if (ImGui::Begin("Game Stats")) { ImGui::Text("Score: %d", _score); ImGui::Text("Health: %.0f%%", _health * 100.0f); if (ImGui::Button("Pause")) { toggle_pause(); } } ImGui::End(); } ``` **Respecting Input Capture:** ```cpp void Game::update() { // Don't process game input when ImGui wants it if (!engine.imgui_system().want_capture_mouse()) { handle_mouse_input(); } if (!engine.imgui_system().want_capture_keyboard()) { handle_keyboard_input(); } } ``` ### DPI Scaling The system automatically handles HiDPI displays: 1. **DPI Detection**: Computed from swapchain extent vs window size ratio. 2. **Font Scaling**: Base font size (16px) scaled by DPI factor. 3. **Global Scale**: `FontGlobalScale` set to 1/DPI for proper sizing. 4. **Dynamic Updates**: Fonts rebuilt when DPI changes (e.g., monitor switch). DPI scale range: 0.5x to 4.0x (clamped for stability). ### Vulkan Integration ImGui is initialized with: - **Dynamic Rendering**: No render pass objects, uses `VK_KHR_dynamic_rendering`. - **Dedicated Descriptor Pool**: Separate pool with generous limits for ImGui textures. - **Swapchain Format**: Renders directly to swapchain image format. ```cpp ImGui_ImplVulkan_InitInfo init_info{}; init_info.Instance = device->instance(); init_info.PhysicalDevice = device->physicalDevice(); init_info.Device = device->device(); init_info.QueueFamily = device->graphicsQueueFamily(); init_info.Queue = device->graphicsQueue(); init_info.DescriptorPool = _imgui_pool; init_info.MinImageCount = swapchain_image_count; init_info.ImageCount = swapchain_image_count; init_info.UseDynamicRendering = true; init_info.PipelineRenderingCreateInfo.colorAttachmentCount = 1; init_info.PipelineRenderingCreateInfo.pColorAttachmentFormats = &swapchain_format; init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; ``` ### RenderGraph Integration ImGui rendering is handled by `ImGuiPass`: ```cpp // In RenderGraph build _imgui_pass.register_graph(graph, swapchain_image_handle); // Pass executes after all other rendering // Renders ImGui draw data to swapchain image ``` The pass: 1. Begins dynamic rendering on swapchain image. 2. Calls `ImGui_ImplVulkan_RenderDrawData()`. 3. Ends rendering. ### Built-in Debug UI The engine provides comprehensive debug widgets in `engine_ui.cpp`: **Window Tab:** - Monitor selection and fullscreen modes. - HiDPI status and size information. - GPU information display. **Stats Tab:** - Frame time and FPS. - Draw call and triangle counts. - Memory usage statistics. **Scene Tab:** - GLTF instance spawning. - Primitive mesh spawning. - Point light editor. - Object transform manipulation (ImGuizmo). **Render Graph Tab:** - Pass list with toggle controls. - Resource tracking visualization. - Barrier inspection. **Texture Streaming Tab:** - VRAM budget and usage. - Texture load queue status. - Cache statistics. **Shadows Tab:** - Shadow mode selection. - Cascade visualization. - Ray-tracing hybrid controls. **Post Processing Tab:** - Tonemapping settings. - Bloom controls. - FXAA parameters. - SSR configuration. ### Draw Callback Order Callbacks are invoked in registration order during `begin_frame()`: ```cpp void ImGuiSystem::begin_frame() { ImGui_ImplVulkan_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // Invoke all registered callbacks for (auto& cb : _draw_callbacks) { if (cb) cb(); } } ``` Register order-dependent callbacks carefully: ```cpp // Engine debug UI first imgui.add_draw_callback([]{ draw_engine_ui(); }); // Game UI on top imgui.add_draw_callback([]{ draw_game_ui(); }); // Editor overlays last imgui.add_draw_callback([]{ draw_editor_overlays(); }); ``` ### ImGuizmo Integration The engine integrates ImGuizmo for 3D gizmo manipulation: ```cpp #include "ImGuizmo.h" void draw_object_gizmo(const glm::mat4& view, const glm::mat4& proj, glm::mat4& object_transform) { ImGuizmo::SetOrthographic(false); ImGuizmo::SetDrawlist(); ImGuiIO& io = ImGui::GetIO(); ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); static ImGuizmo::OPERATION op = ImGuizmo::TRANSLATE; static ImGuizmo::MODE mode = ImGuizmo::WORLD; ImGuizmo::Manipulate( glm::value_ptr(view), glm::value_ptr(proj), op, mode, glm::value_ptr(object_transform)); } ``` ### Tips - Always check `want_capture_mouse()` before processing game mouse input. - Use `want_capture_keyboard()` before processing game keyboard input. - Register draw callbacks during initialization, not every frame. - Call `on_swapchain_recreated()` after window resize/mode change. - The descriptor pool is sized for 1000 sets of each type — sufficient for most debug UIs. - For production games, consider conditionally compiling out debug UI. - ImGui windows are persistent between frames — state is preserved automatically. ### Frame Flow 1. **Event Processing**: `process_event()` for each SDL event. 2. **Begin Frame**: `begin_frame()` starts new ImGui frame and invokes callbacks. 3. **UI Building**: All `ImGui::*` calls happen inside draw callbacks. 4. **End Frame**: `end_frame()` calls `ImGui::Render()` to finalize draw data. 5. **RenderGraph**: `ImGuiPass` executes, recording draw commands to GPU. 6. **Present**: Swapchain presents the final image with ImGui overlay.