314 lines
7.5 KiB
Markdown
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. |