Files
QuaternionEngine/docs/ImGuiSystem.md
2025-12-17 01:43:13 +09:00

314 lines
7.5 KiB
Markdown

## 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<void()>;
```
**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.