The open source OpenXR runtime
at main 435 lines 9.4 kB view raw
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 */