Skip to content

Commit cff7583

Browse files
committed
Dear ImGui
1 parent 8447590 commit cff7583

File tree

11 files changed

+386
-3
lines changed

11 files changed

+386
-3
lines changed

ext/CMakeLists.txt

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,47 @@ target_compile_definitions(glm PUBLIC
2727
message(STATUS "[Vulkan-Headers]")
2828
add_subdirectory(src/Vulkan-Headers)
2929

30+
# setup Dear ImGui library
31+
message(STATUS "[Dear ImGui]")
32+
add_library(imgui)
33+
add_library(imgui::imgui ALIAS imgui)
34+
target_include_directories(imgui SYSTEM PUBLIC src/imgui)
35+
target_link_libraries(imgui PUBLIC
36+
glfw::glfw
37+
Vulkan::Headers
38+
)
39+
target_compile_definitions(imgui PUBLIC
40+
VK_NO_PROTOTYPES # Dynamically load Vulkan at runtime
41+
)
42+
target_sources(imgui PRIVATE
43+
src/imgui/imconfig.h
44+
src/imgui/imgui_demo.cpp
45+
src/imgui/imgui_draw.cpp
46+
src/imgui/imgui_internal.h
47+
src/imgui/imgui_tables.cpp
48+
src/imgui/imgui_widgets.cpp
49+
src/imgui/imgui.cpp
50+
src/imgui/imgui.h
51+
52+
src/imgui/backends/imgui_impl_glfw.cpp
53+
src/imgui/backends/imgui_impl_glfw.h
54+
src/imgui/backends/imgui_impl_vulkan.cpp
55+
src/imgui/backends/imgui_impl_vulkan.h
56+
)
57+
3058
# declare ext library target
3159
add_library(${PROJECT_NAME} INTERFACE)
3260
add_library(learn-vk::ext ALIAS ${PROJECT_NAME})
3361

3462
# link to all dependencies
3563
target_link_libraries(${PROJECT_NAME} INTERFACE
36-
glfw::glfw
3764
glm::glm
38-
Vulkan::Headers
65+
imgui::imgui
3966
)
4067

4168
# setup preprocessor defines
4269
target_compile_definitions(${PROJECT_NAME} INTERFACE
4370
GLFW_INCLUDE_VULKAN # enable GLFW's Vulkan API
44-
VK_NO_PROTOTYPES # Dynamically load Vulkan at runtime
4571
)
4672

4773
if(CMAKE_SYSTEM_NAME STREQUAL Linux)

guide/src/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@
2121
- [Render Sync](rendering/render_sync.md)
2222
- [Swapchain Update](rendering/swapchain_update.md)
2323
- [Dynamic Rendering](rendering/dynamic_rendering.md)
24+
- [Dear ImGui](dear_imgui/README.md)
25+
- [class DearImGui](dear_imgui/dear_imgui.md)
26+
- [ImGui Integration](dear_imgui/imgui_integration.md)

guide/src/dear_imgui/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Dear ImGui
2+
3+
Dear ImGui does not have native CMake support, and while adding the sources to the executable is an option, we will add it as an external library target: `imgui` to isolate it (and compile warnings etc) from our own code. This requires some changes to the `ext` target structure, since `imgui` will itself need to link to GLFW and Vulkan-Headers, have `VK_NO_PROTOTYPES` defined, etc. `learn-vk-ext` then links to `imgui` and any other libraries (currently only `glm`). We are using Dear ImGui v1.91.9, which has decent support for Dynamic Rendering.

guide/src/dear_imgui/dear_imgui.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# class DearImGui
2+
3+
Dear ImGui has its own initialization and loop, which we encapsulate into `class DearImGui`:
4+
5+
```cpp
6+
struct DearImGuiCreateInfo {
7+
GLFWwindow* window{};
8+
std::uint32_t api_version{};
9+
vk::Instance instance{};
10+
vk::PhysicalDevice physical_device{};
11+
std::uint32_t queue_family{};
12+
vk::Device device{};
13+
vk::Queue queue{};
14+
vk::Format color_format{}; // single color attachment.
15+
vk::SampleCountFlagBits samples{};
16+
};
17+
18+
class DearImGui {
19+
public:
20+
using CreateInfo = DearImGuiCreateInfo;
21+
22+
explicit DearImGui(CreateInfo const& create_info);
23+
24+
void new_frame();
25+
void end_frame();
26+
void render(vk::CommandBuffer command_buffer) const;
27+
28+
private:
29+
enum class State : std::int8_t { Ended, Begun };
30+
31+
struct Deleter {
32+
void operator()(vk::Device device) const;
33+
};
34+
35+
State m_state{};
36+
37+
Scoped<vk::Device, Deleter> m_device{};
38+
};
39+
```
40+
41+
In the constructor, we start by creating the ImGui Context, loading Vulkan functions, and initializing GLFW for Vulkan:
42+
43+
```cpp
44+
IMGUI_CHECKVERSION();
45+
ImGui::CreateContext();
46+
47+
static auto const load_vk_func = +[](char const* name, void* user_data) {
48+
return VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr(
49+
*static_cast<vk::Instance*>(user_data), name);
50+
};
51+
auto instance = create_info.instance;
52+
ImGui_ImplVulkan_LoadFunctions(create_info.api_version, load_vk_func,
53+
&instance);
54+
55+
if (!ImGui_ImplGlfw_InitForVulkan(create_info.window, true)) {
56+
throw std::runtime_error{"Failed to initialize Dear ImGui"};
57+
}
58+
```
59+
60+
Then initialize Dear ImGui for Vulkan:
61+
62+
```cpp
63+
auto init_info = ImGui_ImplVulkan_InitInfo{};
64+
init_info.ApiVersion = create_info.api_version;
65+
init_info.Instance = create_info.instance;
66+
init_info.PhysicalDevice = create_info.physical_device;
67+
init_info.Device = create_info.device;
68+
init_info.QueueFamily = create_info.queue_family;
69+
init_info.Queue = create_info.queue;
70+
init_info.MinImageCount = 2;
71+
init_info.ImageCount = static_cast<std::uint32_t>(resource_buffering_v);
72+
init_info.MSAASamples =
73+
static_cast<VkSampleCountFlagBits>(create_info.samples);
74+
init_info.DescriptorPoolSize = 2;
75+
auto pipline_rendering_ci = vk::PipelineRenderingCreateInfo{};
76+
pipline_rendering_ci.setColorAttachmentCount(1).setColorAttachmentFormats(
77+
create_info.color_format);
78+
init_info.PipelineRenderingCreateInfo = pipline_rendering_ci;
79+
init_info.UseDynamicRendering = true;
80+
if (!ImGui_ImplVulkan_Init(&init_info)) {
81+
throw std::runtime_error{"Failed to initialize Dear ImGui"};
82+
}
83+
ImGui_ImplVulkan_CreateFontsTexture();
84+
```
85+
86+
Since we are using an sRGB format and Dear ImGui is not color-space aware, we need to convert its style colors to linear space (so that they shift back to the original values by gamma correction):
87+
88+
```cpp
89+
ImGui::StyleColorsDark();
90+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
91+
for (auto& colour : ImGui::GetStyle().Colors) {
92+
auto const linear = glm::convertSRGBToLinear(
93+
glm::vec4{colour.x, colour.y, colour.z, colour.w});
94+
colour = ImVec4{linear.x, linear.y, linear.z, linear.w};
95+
}
96+
ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.99f; // more opaque
97+
```
98+
99+
Finally, create the deleter and its implementation:
100+
101+
```cpp
102+
m_device = Scoped<vk::Device, Deleter>{create_info.device};
103+
104+
// ...
105+
void DearImGui::Deleter::operator()(vk::Device const device) const {
106+
device.waitIdle();
107+
ImGui_ImplVulkan_DestroyFontsTexture();
108+
ImGui_ImplVulkan_Shutdown();
109+
ImGui_ImplGlfw_Shutdown();
110+
ImGui::DestroyContext();
111+
}
112+
```
113+
114+
The remaining functions are straightforward:
115+
116+
```cpp
117+
void DearImGui::new_frame() {
118+
if (m_state == State::Begun) { end_frame(); }
119+
ImGui_ImplGlfw_NewFrame();
120+
ImGui_ImplVulkan_NewFrame();
121+
ImGui::NewFrame();
122+
m_state = State::Begun;
123+
}
124+
125+
void DearImGui::end_frame() {
126+
if (m_state == State::Ended) { return; }
127+
ImGui::Render();
128+
m_state = State::Ended;
129+
}
130+
131+
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
132+
void DearImGui::render(vk::CommandBuffer const command_buffer) const {
133+
auto* data = ImGui::GetDrawData();
134+
if (data == nullptr) { return; }
135+
ImGui_ImplVulkan_RenderDrawData(data, command_buffer);
136+
}
137+
```

guide/src/dear_imgui/imgui_demo.png

23.5 KB
Loading
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# ImGui Integration
2+
3+
Update `Swapchain` to expose its image format:
4+
5+
```cpp
6+
[[nodiscard]] auto get_format() const -> vk::Format {
7+
return m_ci.imageFormat;
8+
}
9+
```
10+
11+
`class App` can now store a `std::optional<DearImGui>` member and add/call its create function:
12+
13+
```cpp
14+
void App::create_imgui() {
15+
auto const imgui_ci = DearImGui::CreateInfo{
16+
.window = m_window.get(),
17+
.api_version = vk_version_v,
18+
.instance = *m_instance,
19+
.physical_device = m_gpu.device,
20+
.queue_family = m_gpu.queue_family,
21+
.device = *m_device,
22+
.queue = m_queue,
23+
.color_format = m_swapchain->get_format(),
24+
.samples = vk::SampleCountFlagBits::e1,
25+
};
26+
m_imgui.emplace(imgui_ci);
27+
}
28+
```
29+
30+
Start a new ImGui frame after resetting the render fence, and show the demo window:
31+
32+
```cpp
33+
m_device->resetFences(*render_sync.drawn);
34+
m_imgui->new_frame();
35+
36+
ImGui::ShowDemoWindow();
37+
```
38+
39+
We use a separate render pass for Dear ImGui, again for isolation, and to enable us to change the main render pass later, eg by adding a depth buffer attachment (`DearImGui` is setup assuming its render pass will only use a single color attachment).
40+
41+
```cpp
42+
m_imgui->end_frame();
43+
rendering_info.setColorAttachments(attachment_info)
44+
.setPDepthAttachment(nullptr);
45+
render_sync.command_buffer.beginRendering(rendering_info);
46+
m_imgui->render(render_sync.command_buffer);
47+
render_sync.command_buffer.endRendering();
48+
```
49+
50+
![ImGui Demo](./imgui_demo.png)

src/app.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ void App::run() {
1717
create_device();
1818
create_swapchain();
1919
create_render_sync();
20+
create_imgui();
2021

2122
main_loop();
2223
}
@@ -127,6 +128,21 @@ void App::create_render_sync() {
127128
}
128129
}
129130

131+
void App::create_imgui() {
132+
auto const imgui_ci = DearImGui::CreateInfo{
133+
.window = m_window.get(),
134+
.api_version = vk_version_v,
135+
.instance = *m_instance,
136+
.physical_device = m_gpu.device,
137+
.queue_family = m_gpu.queue_family,
138+
.device = *m_device,
139+
.queue = m_queue,
140+
.color_format = m_swapchain->get_format(),
141+
.samples = vk::SampleCountFlagBits::e1,
142+
};
143+
m_imgui.emplace(imgui_ci);
144+
}
145+
130146
void App::main_loop() {
131147
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
132148
glfwPollEvents();
@@ -155,6 +171,9 @@ void App::main_loop() {
155171
// reset fence _after_ acquisition of image: if it fails, the
156172
// fence remains signaled.
157173
m_device->resetFences(*render_sync.drawn);
174+
m_imgui->new_frame();
175+
176+
ImGui::ShowDemoWindow();
158177

159178
auto command_buffer_bi = vk::CommandBufferBeginInfo{};
160179
// this flag means recorded commands will not be reused.
@@ -195,6 +214,13 @@ void App::main_loop() {
195214
// draw stuff here.
196215
render_sync.command_buffer.endRendering();
197216

217+
m_imgui->end_frame();
218+
rendering_info.setColorAttachments(attachment_info)
219+
.setPDepthAttachment(nullptr);
220+
render_sync.command_buffer.beginRendering(rendering_info);
221+
m_imgui->render(render_sync.command_buffer);
222+
render_sync.command_buffer.endRendering();
223+
198224
// AttachmentOptimal => PresentSrc
199225
// the barrier must wait for color attachment operations to complete.
200226
// we don't need any post-synchronization as the present Sempahore takes

src/app.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
2+
#include <dear_imgui.hpp>
23
#include <gpu.hpp>
34
#include <resource_buffering.hpp>
45
#include <scoped_waiter.hpp>
@@ -30,6 +31,7 @@ class App {
3031
void create_device();
3132
void create_swapchain();
3233
void create_render_sync();
34+
void create_imgui();
3335

3436
void main_loop();
3537

@@ -48,6 +50,8 @@ class App {
4850
// Current virtual frame index.
4951
std::size_t m_frame_index{};
5052

53+
std::optional<DearImGui> m_imgui{};
54+
5155
ScopedWaiter m_waiter{};
5256
};
5357
} // namespace lvk

0 commit comments

Comments
 (0)