Skip to content

Commit c5523a3

Browse files
cursoragenttimfox
andcommitted
ImGui: Vulkan render backend for debug inspectors (overlay pass)
Wire Dear ImGui through imgui_impl_vulkan with engine qvk* loader resolution, record UI into overlay_compose after gamma pass. Add r_imgui toggle (default 1), SDL mouse sampling when enabled, swapchain restart notification, SDL2::Core link. Adds vk_imgui_vulkan.cpp bridge; avoids including tr_local.h in C++ due to reserved-member conflicts. Co-authored-by: Tim Fox <timfox@outlook.com>
1 parent 2db3859 commit c5523a3

8 files changed

Lines changed: 325 additions & 11 deletions

File tree

CMakeLists.txt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,11 +1962,15 @@ if(USE_IMGUI AND NOT APPLE)
19621962
enable_language(CXX)
19631963
set(IMGUI_SRCS
19641964
src/renderers/vulkan/inspector/vk_imgui.cpp
1965+
src/renderers/vulkan/inspector/vk_imgui_vulkan.cpp
19651966
${EXTERNAL_SRC}/cimgui/imgui/imgui.cpp
19661967
${EXTERNAL_SRC}/cimgui/imgui/imgui_draw.cpp
19671968
${EXTERNAL_SRC}/cimgui/imgui/imgui_tables.cpp
19681969
${EXTERNAL_SRC}/cimgui/imgui/imgui_widgets.cpp
1970+
${EXTERNAL_SRC}/cimgui/imgui/backends/imgui_impl_vulkan.cpp
19691971
)
1972+
set_source_files_properties(${EXTERNAL_SRC}/cimgui/imgui/backends/imgui_impl_vulkan.cpp PROPERTIES
1973+
COMPILE_DEFINITIONS "VK_NO_PROTOTYPES")
19701974
set(IMGUI_INCLUDE_DIRS
19711975
${EXTERNAL_SRC}/cimgui/imgui
19721976
${EXTERNAL_SRC}/cimgui/imgui/backends
@@ -1976,12 +1980,18 @@ if(USE_IMGUI AND NOT APPLE)
19761980
if(USE_RENDERER_DLOPEN)
19771981
target_sources(${RENDERER_PREFIX}_vulkan${RENDEXT} PRIVATE ${IMGUI_SRCS})
19781982
target_include_directories(${RENDERER_PREFIX}_vulkan${RENDEXT} PRIVATE ${IMGUI_INCLUDE_DIRS})
1979-
target_compile_definitions(${RENDERER_PREFIX}_vulkan${RENDEXT} PRIVATE USE_IMGUI)
1983+
target_compile_definitions(${RENDERER_PREFIX}_vulkan${RENDEXT} PRIVATE USE_IMGUI USE_VULKAN)
1984+
if(USE_SDL AND TARGET SDL2::Core)
1985+
target_link_libraries(${RENDERER_PREFIX}_vulkan${RENDEXT} PRIVATE SDL2::Core)
1986+
endif()
19801987
else()
19811988
if(USE_VULKAN)
19821989
target_sources(${RENDERER_PREFIX}_vulkan PRIVATE ${IMGUI_SRCS})
19831990
target_include_directories(${RENDERER_PREFIX}_vulkan PRIVATE ${IMGUI_INCLUDE_DIRS})
1984-
target_compile_definitions(${RENDERER_PREFIX}_vulkan PRIVATE USE_IMGUI)
1991+
target_compile_definitions(${RENDERER_PREFIX}_vulkan PRIVATE USE_IMGUI USE_VULKAN)
1992+
if(USE_SDL AND TARGET SDL2::Core)
1993+
target_link_libraries(${RENDERER_PREFIX}_vulkan PRIVATE SDL2::Core)
1994+
endif()
19851995
endif()
19861996
endif()
19871997
message(STATUS "ImGui Inspector: enabled")

src/renderers/vulkan/inspector/vk_imgui.cpp

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ Architecture inspired by EternalJK's pbr-rtx-inspector (Sunny JK).
1313

1414
#ifdef USE_IMGUI
1515

16-
#define VK_NO_PROTOTYPES
1716
#define IMGUI_DEFINE_MATH_OPERATORS
1817

1918
#include <float.h>
2019
#include <imgui.h>
20+
#include <imgui_impl_vulkan.h>
2121
#include <imgui_internal.h>
2222

2323
extern "C" {
@@ -26,10 +26,26 @@ extern "C" {
2626
#include "../../renderers/common/tr_public.h"
2727

2828
extern glconfig_t glConfig;
29+
extern cvar_t *r_imgui;
2930
}
3031

32+
#ifdef USE_VULKAN
33+
#define USE_VK_PBR
34+
#include "../vk.h"
35+
#endif
36+
3137
#include "vk_imgui.h"
3238

39+
extern "C" bool VkImgui_InitVulkanBackend( ImGui_ImplVulkan_InitInfo *outInfo, char *errBuf, size_t errBufSize );
40+
extern "C" void VkImgui_ShutdownVulkanBackend( void );
41+
extern "C" void VkImgui_NewFrameVulkan( void );
42+
extern "C" void VkImgui_RenderDrawDataVulkan( ImDrawData *drawData, VkCommandBuffer cmd );
43+
extern "C" void VkImgui_UpdateMouseFromSDL( ImGuiIO *io, qboolean inspectorWantsInput );
44+
extern "C" void VkImgui_NotifySwapchainRestart( void );
45+
extern "C" void VkImgui_SetVulkanBackendReady( qboolean ready );
46+
47+
static qboolean vkImgBackendReady = qfalse;
48+
3349
/* Helper to read float cvar (refimport has no Cvar_VariableValue) */
3450
static float VkImgui_CvarFloat( const char *name )
3551
{
@@ -79,7 +95,16 @@ static void VkImgui_PrepareIO( void )
7995
(float)( windowHeight >= 0 ? windowHeight : 0 ) );
8096
io.DisplayFramebufferScale = ImVec2( 1.0f, 1.0f );
8197
io.DeltaTime = deltaSeconds;
82-
io.MousePos = ImVec2( -FLT_MAX, -FLT_MAX );
98+
99+
{
100+
const qboolean wantInput = ( r_imgui && r_imgui->integer ) ? qtrue : qfalse;
101+
vkImguiState.inputState = wantInput;
102+
if ( wantInput ) {
103+
VkImgui_UpdateMouseFromSDL( &io, wantInput );
104+
} else {
105+
io.MousePos = ImVec2( -FLT_MAX, -FLT_MAX );
106+
}
107+
}
83108

84109
imguiLastFrameTimeMs = nowMs;
85110
}
@@ -249,12 +274,6 @@ extern "C" void VkImgui_Initialize(void) {
249274
VkImgui_SetCurrentContext();
250275
ImGuiIO &io = ImGui::GetIO();
251276
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
252-
/* Build font atlas so ImGui can render text. Required when backend does not set
253-
* ImGuiBackendFlags_RendererHasTextures. Avoids "font atlas is not built" assert. */
254-
unsigned char *fontPixels = nullptr;
255-
int fontW = 0, fontH = 0;
256-
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontW, &fontH);
257-
(void)fontPixels; (void)fontW; (void)fontH; /* backend would upload to GPU and call SetTexID */
258277
VkImgui_DarkTheme();
259278

260279
memset(&vkInspector, 0, sizeof(vkInspector));
@@ -266,13 +285,38 @@ extern "C" void VkImgui_Initialize(void) {
266285
vkImguiState.active = qtrue;
267286
vkImguiState.inputState = qfalse;
268287
imguiLastFrameTimeMs = 0;
288+
289+
#ifdef USE_VULKAN
290+
{
291+
char errBuf[256];
292+
293+
vkImgBackendReady = qfalse;
294+
VkImgui_SetVulkanBackendReady( qfalse );
295+
if ( vk.device != VK_NULL_HANDLE && vk.render_pass.overlay_compose != VK_NULL_HANDLE ) {
296+
if ( VkImgui_InitVulkanBackend( nullptr, errBuf, sizeof( errBuf ) ) ) {
297+
vkImgBackendReady = qtrue;
298+
VkImgui_SetVulkanBackendReady( qtrue );
299+
ri.Printf( PRINT_ALL, "ImGui: Vulkan renderer backend initialized (overlay pass)\n" );
300+
} else {
301+
ri.Printf( PRINT_WARNING, "ImGui: Vulkan renderer backend failed (%s)\n", errBuf );
302+
}
303+
}
304+
}
305+
#endif
269306
}
270307

271308
extern "C" void VkImgui_Shutdown(void) {
272309
if (!vkImguiState.active) return;
273310

274311
if (imguiContext) {
275312
VkImgui_SetCurrentContext();
313+
#ifdef USE_VULKAN
314+
if ( vkImgBackendReady ) {
315+
VkImgui_ShutdownVulkanBackend();
316+
vkImgBackendReady = qfalse;
317+
VkImgui_SetVulkanBackendReady( qfalse );
318+
}
319+
#endif
276320
ImGui::DestroyContext(imguiContext);
277321
imguiContext = nullptr;
278322
}
@@ -285,6 +329,11 @@ extern "C" void VkImgui_BeginFrame(void) {
285329
if (!vkImguiState.active) return;
286330
VkImgui_SetCurrentContext();
287331
VkImgui_PrepareIO();
332+
#ifdef USE_VULKAN
333+
if ( vkImgBackendReady ) {
334+
VkImgui_NewFrameVulkan();
335+
}
336+
#endif
288337
ImGui::NewFrame();
289338
}
290339

@@ -914,7 +963,12 @@ extern "C" void VkImgui_Draw(void) {
914963
ImGui::Render();
915964
}
916965

917-
extern "C" void VkImgui_SwapchainRestarted(void) { }
966+
extern "C" void VkImgui_SwapchainRestarted(void) {
967+
#ifdef USE_VULKAN
968+
VkImgui_NotifySwapchainRestart();
969+
#endif
970+
}
971+
918972
extern "C" void VkImgui_BindGameColorImage(void) { }
919973

920974
#endif /* USE_IMGUI */

src/renderers/vulkan/inspector/vk_imgui.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ void VkImgui_DrawVolumetricsPanel(void);
170170

171171
void VkImgui_BindGameColorImage(void);
172172

173+
/* Record ImGui into the swapchain (overlay_compose pass). Called from vk_frame_end after gamma. */
174+
void VkImgui_RecordOverlayPass( void );
175+
176+
qboolean VkImgui_IsVulkanBackendReady( void );
177+
void VkImgui_SetVulkanBackendReady( qboolean ready );
178+
179+
void VkImgui_NotifySwapchainRestart( void );
180+
173181
#ifdef __cplusplus
174182
}
175183
#endif
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
===========================================================================
3+
Copyright (C) 2026 Gopex LLC. All rights reserved.
4+
5+
Bridge Dear ImGui to the engine's Vulkan loader (qvk* via ImGui_ImplVulkan_LoadFunctions).
6+
SDL2 mouse position when the inspector wants input.
7+
===========================================================================
8+
*/
9+
10+
#ifdef USE_IMGUI
11+
12+
#define VK_NO_PROTOTYPES
13+
14+
#include <imgui.h>
15+
#include <imgui_impl_vulkan.h>
16+
17+
extern "C" {
18+
#include "../../qcommon/q_shared.h"
19+
#include "../vk_instance.h"
20+
#define USE_VK_PBR
21+
#include "../vk.h"
22+
#include "../vk_render_pass.h"
23+
}
24+
25+
#if defined( USE_SDL ) && !defined( ANDROID )
26+
# if defined( USE_LOCAL_HEADERS )
27+
# include "SDL.h"
28+
# else
29+
# include <SDL2/SDL.h>
30+
# endif
31+
extern "C" struct SDL_Window *SDL_window;
32+
#endif
33+
34+
#include "vk_imgui.h"
35+
36+
static qboolean g_vkImguiBackendReady = qfalse;
37+
38+
extern "C" void VkImgui_SetVulkanBackendReady( qboolean ready )
39+
{
40+
g_vkImguiBackendReady = ready;
41+
}
42+
43+
extern "C" qboolean VkImgui_IsVulkanBackendReady( void )
44+
{
45+
return g_vkImguiBackendReady;
46+
}
47+
48+
static PFN_vkVoidFunction VkImgui_ResolveVulkanProc( const char *name, void *userData )
49+
{
50+
PFN_vkVoidFunction p = nullptr;
51+
(void)userData;
52+
53+
if ( ri.VK_GetInstanceProcAddr && vk_instance != VK_NULL_HANDLE ) {
54+
p = (PFN_vkVoidFunction)ri.VK_GetInstanceProcAddr( vk_instance, name );
55+
}
56+
if ( p == nullptr && qvkGetDeviceProcAddr != nullptr && vk.device != VK_NULL_HANDLE ) {
57+
p = (PFN_vkVoidFunction)qvkGetDeviceProcAddr( vk.device, name );
58+
}
59+
return p;
60+
}
61+
62+
extern "C" bool VkImgui_InitVulkanBackend( ImGui_ImplVulkan_InitInfo *outInfo, char *errBuf, size_t errBufSize )
63+
{
64+
ImGui_ImplVulkan_InitInfo info;
65+
66+
if ( errBuf && errBufSize > 0 ) {
67+
errBuf[0] = '\0';
68+
}
69+
70+
if ( vk_instance == VK_NULL_HANDLE || vk.device == VK_NULL_HANDLE || vk.queue == VK_NULL_HANDLE ||
71+
vk.physical_device == VK_NULL_HANDLE ) {
72+
if ( errBuf && errBufSize > 0 ) {
73+
Q_strncpyz( errBuf, "Vulkan device not ready", errBufSize );
74+
}
75+
return false;
76+
}
77+
78+
if ( !ImGui_ImplVulkan_LoadFunctions( 0, VkImgui_ResolveVulkanProc, nullptr ) ) {
79+
if ( errBuf && errBufSize > 0 ) {
80+
Q_strncpyz( errBuf, "ImGui_ImplVulkan_LoadFunctions failed", errBufSize );
81+
}
82+
return false;
83+
}
84+
85+
Com_Memset( &info, 0, sizeof( info ) );
86+
#ifdef VK_API_VERSION_1_4
87+
info.ApiVersion = VK_API_VERSION_1_4;
88+
#elif defined( VK_API_VERSION_1_3 )
89+
info.ApiVersion = VK_API_VERSION_1_3;
90+
#else
91+
info.ApiVersion = VK_API_VERSION_1_2;
92+
#endif
93+
info.Instance = vk_instance;
94+
info.PhysicalDevice = vk.physical_device;
95+
info.Device = vk.device;
96+
info.QueueFamily = vk.queue_family_index;
97+
info.Queue = vk.queue;
98+
info.DescriptorPool = VK_NULL_HANDLE;
99+
info.DescriptorPoolSize = 64;
100+
info.MinImageCount = 2;
101+
info.ImageCount = vk.swapchain_image_count > 0 ? vk.swapchain_image_count : 2;
102+
if ( info.ImageCount < info.MinImageCount ) {
103+
info.ImageCount = info.MinImageCount;
104+
}
105+
info.PipelineCache = VK_NULL_HANDLE;
106+
info.PipelineInfoMain.RenderPass = vk.render_pass.overlay_compose;
107+
info.PipelineInfoMain.Subpass = 0;
108+
info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
109+
info.UseDynamicRendering = false;
110+
111+
if ( ImGui_ImplVulkan_Init( &info ) ) {
112+
if ( outInfo ) {
113+
*outInfo = info;
114+
}
115+
return true;
116+
}
117+
118+
if ( errBuf && errBufSize > 0 ) {
119+
Q_strncpyz( errBuf, "ImGui_ImplVulkan_Init failed", errBufSize );
120+
}
121+
return false;
122+
}
123+
124+
extern "C" void VkImgui_ShutdownVulkanBackend( void )
125+
{
126+
ImGui_ImplVulkan_Shutdown();
127+
}
128+
129+
extern "C" void VkImgui_NewFrameVulkan( void )
130+
{
131+
ImGui_ImplVulkan_NewFrame();
132+
}
133+
134+
extern "C" void VkImgui_RenderDrawDataVulkan( ImDrawData *drawData, VkCommandBuffer cmd )
135+
{
136+
if ( drawData != nullptr && cmd != VK_NULL_HANDLE ) {
137+
ImGui_ImplVulkan_RenderDrawData( drawData, cmd );
138+
}
139+
}
140+
141+
extern "C" void VkImgui_UpdateMouseFromSDL( ImGuiIO *io, qboolean inspectorWantsInput )
142+
{
143+
#if defined( USE_SDL ) && !defined( ANDROID )
144+
if ( !inspectorWantsInput || io == nullptr || SDL_window == nullptr ) {
145+
return;
146+
}
147+
{
148+
int mx = 0;
149+
int my = 0;
150+
const Uint32 buttons = SDL_GetMouseState( &mx, &my );
151+
io->MousePos = ImVec2( (float)mx, (float)my );
152+
io->MouseDown[0] = ( buttons & SDL_BUTTON( SDL_BUTTON_LEFT ) ) != 0;
153+
io->MouseDown[1] = ( buttons & SDL_BUTTON( SDL_BUTTON_RIGHT ) ) != 0;
154+
io->MouseDown[2] = ( buttons & SDL_BUTTON( SDL_BUTTON_MIDDLE ) ) != 0;
155+
}
156+
#else
157+
(void)io;
158+
(void)inspectorWantsInput;
159+
#endif
160+
}
161+
162+
extern "C" void VkImgui_NotifySwapchainRestart( void )
163+
{
164+
if ( vk.swapchain_image_count >= 2 ) {
165+
ImGui_ImplVulkan_SetMinImageCount( vk.swapchain_image_count );
166+
}
167+
}
168+
169+
extern "C" void VkImgui_RecordOverlayPass( void )
170+
{
171+
extern cvar_t *r_imgui;
172+
173+
if ( !g_vkImguiBackendReady || !vkImguiState.active ) {
174+
return;
175+
}
176+
if ( !r_imgui || !r_imgui->integer ) {
177+
return;
178+
}
179+
if ( ImGui::GetCurrentContext() == nullptr ) {
180+
return;
181+
}
182+
if ( vk.cmd == nullptr || vk.cmd->command_buffer == VK_NULL_HANDLE ) {
183+
return;
184+
}
185+
if ( vk.render_pass.overlay_compose == VK_NULL_HANDLE ||
186+
vk.cmd->swapchain_image_index >= MAX_SWAPCHAIN_IMAGES ||
187+
vk.framebuffers.overlay_compose[ vk.cmd->swapchain_image_index ] == VK_NULL_HANDLE ||
188+
vk.renderWidth == 0 || vk.renderHeight == 0 ) {
189+
return;
190+
}
191+
192+
ImDrawData *dd = ImGui::GetDrawData();
193+
if ( dd == nullptr || !dd->Valid ) {
194+
return;
195+
}
196+
197+
vk_begin_render_pass_tracked( vk.render_pass.overlay_compose,
198+
vk.framebuffers.overlay_compose[ vk.cmd->swapchain_image_index ],
199+
qfalse, vk.renderWidth, vk.renderHeight );
200+
ImGui_ImplVulkan_RenderDrawData( dd, vk.cmd->command_buffer );
201+
vk_end_render_pass_tracked();
202+
}
203+
204+
#endif /* USE_IMGUI */

0 commit comments

Comments
 (0)