The open source OpenXR runtime
1// Copyright 2019-2024, Collabora, Ltd.
2// Copyright 2024-2025, NVIDIA CORPORATION.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief SDL2 Debug UI implementation
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 * @author Moshi Turner <moshiturner@protonmail.com>
9 */
10
11#include "math/m_api.h"
12#include "xrt/xrt_system.h"
13#include "xrt/xrt_instance.h"
14#include "xrt/xrt_config_build.h"
15
16#ifndef XRT_FEATURE_DEBUG_GUI
17
18struct u_debug_gui;
19
20int
21u_debug_gui_create(const struct u_debug_gui_create_info *udgci, struct u_debug_gui **out_debug_ui)
22{
23 return 0;
24}
25
26void
27u_debug_gui_start(struct u_debug_gui *debug_ui, struct xrt_instance *xinst, struct xrt_system_devices *xsysd)
28{
29 // No-op
30}
31
32void
33u_debug_gui_stop(struct u_debug_gui **debug_ui)
34{
35 // No-op
36}
37
38#else /* XRT_FEATURE_DEBUG_GUI */
39
40#include "xrt/xrt_config_have.h"
41#include "xrt/xrt_config_drivers.h"
42
43#include "os/os_threading.h"
44
45#include "util/u_var.h"
46#include "util/u_misc.h"
47#include "util/u_file.h"
48#include "util/u_debug.h"
49#include "util/u_debug_gui.h"
50#include "util/u_trace_marker.h"
51
52#include "ogl/ogl_api.h"
53
54#include "gui/gui_common.h"
55#include "gui/gui_imgui.h"
56
57#ifdef XRT_BUILD_DRIVER_QWERTY
58#include "qwerty_interface.h"
59#endif
60
61#include <SDL2/SDL.h>
62
63DEBUG_GET_ONCE_BOOL_OPTION(gui, "XRT_DEBUG_GUI", false)
64#ifdef XRT_BUILD_DRIVER_QWERTY
65DEBUG_GET_ONCE_BOOL_OPTION(qwerty_enable, "QWERTY_ENABLE", false)
66#endif
67
68
69/*!
70 * Common struct holding state for the GUI interface.
71 * @extends gui_program
72 */
73struct u_debug_gui
74{
75 struct gui_program base;
76
77 //! Information passed in at create.
78 struct u_debug_gui_create_info udgci;
79
80 SDL_GLContext ctx;
81 SDL_Window *win;
82
83 struct os_thread_helper oth;
84
85 bool sdl_initialized;
86 char layout_file[1024];
87
88 float gui_scale;
89
90#ifdef XRT_BUILD_DRIVER_QWERTY
91 bool qwerty_enabled;
92#endif
93};
94
95struct gui_imgui
96{
97 bool show_imgui_demo;
98 bool show_implot_demo;
99 struct xrt_colour_rgb_f32 clear;
100};
101
102static void
103sdl2_window_init(struct u_debug_gui *p)
104{
105 XRT_TRACE_MARKER();
106
107 int x = SDL_WINDOWPOS_UNDEFINED;
108 int y = SDL_WINDOWPOS_UNDEFINED;
109 int w = 1920;
110 int h = 1080;
111
112 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
113 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
114 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
115 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
116 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
117 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
118 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
119 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
120
121 int window_flags = 0;
122 window_flags |= SDL_WINDOW_SHOWN;
123 window_flags |= SDL_WINDOW_OPENGL;
124 window_flags |= SDL_WINDOW_RESIZABLE;
125 window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
126#if 0
127 window_flags |= SDL_WINDOW_MAXIMIZED;
128#endif
129
130 p->win = SDL_CreateWindow(p->udgci.window_title, x, y, w, h, window_flags);
131 if (p->win == NULL) {
132 U_LOG_E("Failed to create window!");
133 return;
134 }
135
136 p->ctx = SDL_GL_CreateContext(p->win);
137 if (p->ctx == NULL) {
138 U_LOG_E("Failed to create GL context!");
139 return;
140 }
141
142 SDL_GL_MakeCurrent(p->win, p->ctx);
143 SDL_GL_SetSwapInterval(1); // Enable vsync
144
145 // Setup OpenGL bindings.
146 bool err = gladLoadGL((GLADloadfunc)SDL_GL_GetProcAddress) == 0;
147 if (err) {
148 U_LOG_E("Failed to load GL functions!");
149 return;
150 }
151
152 // To manage the scenes.
153 gui_scene_manager_init(&p->base);
154
155 // Start the scene.
156 gui_scene_debug(&p->base);
157
158 // Set imgui interface scale percentage
159 p->gui_scale = 1.0f; // Fallback (default) value
160 int disp_idx = SDL_GetWindowDisplayIndex(p->win);
161 if (disp_idx >= 0) {
162 float dpi;
163 if (SDL_GetDisplayDPI(disp_idx, &dpi, NULL, NULL) >= 0) {
164 // 96.0f is the base 100% GUI scale reported by SDL2
165 p->gui_scale = CLAMP(dpi / 96.0f, 1.0f, 3.0f); // up to 300%
166 }
167 }
168}
169
170static void
171sdl2_loop_events(struct u_debug_gui *p)
172{
173 XRT_TRACE_MARKER();
174
175 SDL_Event event;
176
177 while (SDL_PollEvent(&event)) {
178 ImGui_ImplSDL2_ProcessEvent(&event);
179
180#ifdef XRT_BUILD_DRIVER_QWERTY
181 // Caution here, qwerty driver is being accessed by the main thread as well
182 if (p->qwerty_enabled) {
183 qwerty_process_event(p->base.xsysd->xdevs, p->base.xsysd->xdev_count, event);
184 }
185#endif
186
187 if (event.type == SDL_QUIT) {
188 p->base.stopped = true;
189 }
190
191 if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE &&
192 event.window.windowID == SDL_GetWindowID(p->win)) {
193 p->base.stopped = true;
194 }
195 }
196}
197
198static void
199sdl2_loop_new_frame(void)
200{
201 XRT_TRACE_MARKER();
202
203 // Start the Dear ImGui frame
204 ImGui_ImplOpenGL3_NewFrame();
205 ImGui_ImplSDL2_NewFrame();
206
207 // Start new frame.
208 igNewFrame();
209}
210
211static void
212sdl2_loop_show_scene(struct u_debug_gui *p, struct gui_imgui *gui)
213{
214 XRT_TRACE_MARKER();
215
216 // Render the scene into it.
217 gui_scene_manager_render(&p->base);
218
219 // Handle this here.
220 if (gui->show_imgui_demo) {
221 igShowDemoWindow(&gui->show_imgui_demo);
222 }
223
224 // Handle this here.
225 if (gui->show_implot_demo) {
226 ImPlot_ShowDemoWindow(&gui->show_implot_demo);
227 }
228}
229
230static void
231sdl2_loop_render(struct u_debug_gui *p, struct gui_imgui *gui, ImGuiIO *io)
232{
233 XRT_TRACE_MARKER();
234
235 // Build the DrawData (EndFrame).
236 igRender();
237
238 // Clear the background.
239 glViewport(0, 0, (int)io->DisplaySize.x, (int)io->DisplaySize.y);
240 glClearColor(gui->clear.r, gui->clear.g, gui->clear.b, 1.0f);
241 glClear(GL_COLOR_BUFFER_BIT);
242
243 ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
244}
245
246static void
247sdl2_loop(struct u_debug_gui *p)
248{
249 // Need to call this before any other Imgui call.
250 igCreateContext(NULL);
251
252 // Local state
253 ImGuiIO *io = igGetIO();
254 io->FontGlobalScale = p->gui_scale;
255
256 // Make window layout file "imgui.ini" live in config dir
257 XRT_MAYBE_UNUSED int res = u_file_get_path_in_config_dir("imgui.ini", p->layout_file, sizeof(p->layout_file));
258 assert(res > 0);
259 io->IniFilename = p->layout_file;
260
261 // Ensure imgui.ini file exists in config dir
262 FILE *imgui_ini = u_file_open_file_in_config_dir("imgui.ini", "a");
263 if (imgui_ini != NULL) {
264 fclose(imgui_ini);
265 }
266
267 // Setup Platform/Renderer bindings
268 ImGui_ImplSDL2_InitForOpenGL(p->win, p->ctx);
269 ImGui_ImplOpenGL3_Init(NULL);
270
271 // Setup Dear ImGui style
272 ImGuiStyle *style = igGetStyle();
273 ImGuiStyle_ScaleAllSizes(style, p->gui_scale);
274 igStyleColorsDark(style);
275
276 // Setup the plot context.
277 ImPlotContext *plot_ctx = ImPlot_CreateContext();
278 ImPlot_SetCurrentContext(plot_ctx);
279
280
281#ifdef XRT_BUILD_DRIVER_QWERTY
282 // Setup qwerty driver usage
283 p->qwerty_enabled = debug_get_bool_option_qwerty_enable();
284#endif
285
286 // Main loop
287 struct gui_imgui gui = {0};
288 gui.clear.r = 0.45f;
289 gui.clear.g = 0.55f;
290 gui.clear.b = 0.60f;
291 u_var_add_root(&gui, "GUI Control", false);
292 u_var_add_rgb_f32(&gui, &gui.clear, "Clear Colour");
293 u_var_add_bool(&gui, &gui.show_imgui_demo, "Imgui Demo Window");
294 u_var_add_bool(&gui, &gui.show_implot_demo, "Implot Demo Window");
295 u_var_add_bool(&gui, &p->base.stopped, "Exit");
296
297 while (!p->base.stopped) {
298 // All this counts as work.
299 XRT_TRACE_IDENT(frame);
300
301 sdl2_loop_events(p);
302
303 sdl2_loop_new_frame();
304
305 sdl2_loop_show_scene(p, &gui);
306
307 sdl2_loop_render(p, &gui, io);
308
309 XRT_TRACE_BEGIN(swap);
310 SDL_GL_SwapWindow(p->win);
311 XRT_TRACE_END(swap);
312 }
313
314 // Cleanup
315 u_var_remove_root(&gui);
316 ImPlot_DestroyContext(plot_ctx);
317 ImGui_ImplOpenGL3_Shutdown();
318 ImGui_ImplSDL2_Shutdown();
319 igDestroyContext(NULL);
320}
321
322static void
323sdl2_close(struct u_debug_gui *p)
324{
325 XRT_TRACE_MARKER();
326
327 // All scenes should be destroyed by now.
328 gui_scene_manager_destroy(&p->base);
329
330 if (p->ctx != NULL) {
331 SDL_GL_DeleteContext(p->ctx);
332 p->ctx = NULL;
333 }
334
335 if (p->win != NULL) {
336 SDL_DestroyWindow(p->win);
337 p->win = NULL;
338 }
339
340 if (p->sdl_initialized) {
341 //! @todo: Properly quit SDL without crashing SDL client apps
342 // SDL_Quit();
343 p->sdl_initialized = false;
344 }
345}
346
347static void *
348u_debug_gui_run_thread(void *ptr)
349{
350 U_TRACE_SET_THREAD_NAME("Debug GUI");
351
352 struct u_debug_gui *debug_gui = (struct u_debug_gui *)ptr;
353 sdl2_window_init(debug_gui);
354
355 sdl2_loop(debug_gui);
356
357 sdl2_close(debug_gui);
358
359 return NULL;
360}
361
362int
363u_debug_gui_create(const struct u_debug_gui_create_info *udgci, struct u_debug_gui **out_debug_gui)
364{
365 XRT_TRACE_MARKER();
366
367 switch (udgci->open) {
368 case U_DEBUG_GUI_OPEN_AUTO:
369 if (!debug_get_bool_option_gui()) {
370 return 0;
371 }
372 break;
373 case U_DEBUG_GUI_OPEN_ALWAYS: break;
374 case U_DEBUG_GUI_OPEN_NEVER: return 0; // No-op
375 }
376
377 // Need to do this as early as possible.
378 u_var_force_on();
379
380 struct u_debug_gui *p = U_TYPED_CALLOC(struct u_debug_gui);
381 if (p == NULL) {
382 return -1;
383 }
384
385 // Copy the data.
386 p->udgci = *udgci;
387
388 os_thread_helper_init(&p->oth);
389
390 *out_debug_gui = p;
391
392 return 0;
393}
394
395void
396u_debug_gui_start(struct u_debug_gui *debug_gui, struct xrt_instance *xinst, struct xrt_system_devices *xsysd)
397{
398 XRT_TRACE_MARKER();
399
400 if (debug_gui == NULL) {
401 return;
402 }
403
404 // Share the system devices.
405 debug_gui->base.xsysd = xsysd;
406
407 if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
408 U_LOG_E("Failed to init SDL2!");
409 return;
410 }
411 debug_gui->sdl_initialized = true;
412
413 (void)os_thread_helper_start(&debug_gui->oth, u_debug_gui_run_thread, (void *)debug_gui);
414}
415
416void
417u_debug_gui_stop(struct u_debug_gui **debug_gui)
418{
419 XRT_TRACE_MARKER();
420
421 struct u_debug_gui *p = *(struct u_debug_gui **)debug_gui;
422 if (p == NULL) {
423 return;
424 }
425
426 p->base.stopped = true;
427
428 // Destroy the thread object.
429 os_thread_helper_destroy(&p->oth);
430
431 free(p);
432 *debug_gui = NULL;
433}
434
435#endif /* XRT_FEATURE_DEBUG_GUI */