The open source OpenXR runtime
at main 1079 lines 29 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 A debugging scene. 7 * @author Jakob Bornecrantz <jakob@collabora.com> 8 * @ingroup gui 9 */ 10 11#include "xrt/xrt_config_have.h" 12 13#include "os/os_time.h" 14 15#include "util/u_var.h" 16#include "util/u_misc.h" 17#include "util/u_sink.h" 18#include "util/u_debug.h" 19#include "util/u_native_images_debug.h" 20 21#ifdef XRT_HAVE_OPENCV 22#include "tracking/t_tracking.h" 23#endif 24 25#include "xrt/xrt_frame.h" 26#include "xrt/xrt_prober.h" 27#include "xrt/xrt_tracking.h" 28#include "xrt/xrt_settings.h" 29#include "xrt/xrt_frameserver.h" 30 31#include "math/m_api.h" 32#include "math/m_filter_fifo.h" 33 34#include "gui_common.h" 35#include "gui_imgui.h" 36#include "gui_window_record.h" 37#include "gui_widget_native_images.h" 38 39#include "imgui_monado/cimgui_monado.h" 40 41#include <float.h> 42#include <inttypes.h> 43 44 45/* 46 * 47 * Structs and defines. 48 * 49 */ 50 51/*! 52 * @defgroup gui_debug Debug GUI 53 * @ingroup gui 54 * 55 * @brief GUI for live inspecting Monado. 56 */ 57 58/*! 59 * A single record window, here only used to draw a single element in a object 60 * window, holds all the needed state. 61 * 62 * @ingroup gui_debug 63 */ 64struct debug_record 65{ 66 void *ptr; 67 68 struct gui_record_window rw; 69}; 70 71/*! 72 * A GUI scene for debugging Monado while it is running, it uses the variable 73 * tracking code in the @ref util/u_var.h file to provide live updates state. 74 * 75 * @implements gui_scene 76 * @ingroup gui_debug 77 */ 78struct debug_scene 79{ 80 struct gui_scene base; 81 struct xrt_frame_context *xfctx; 82 83 struct gui_widget_native_images_storage gwnis; 84 85 struct debug_record recs[32]; 86 uint32_t num_recrs; 87}; 88 89//! How many nested gui headers can we show, overly large. 90#define MAX_HEADER_NESTING 256 91 92//! Shared flags for color gui elements. 93#define COLOR_FLAGS (ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_PickerHueWheel) 94 95/*! 96 * One "frame" of draw state, what is passed to the variable tracking visitor 97 * functions, holds pointers to the program and live state such as visibility 98 * stack of gui headers. 99 * 100 * @ingroup gui_debug 101 */ 102struct draw_state 103{ 104 struct gui_program *p; 105 struct debug_scene *ds; 106 107 //! Visibility stack for nested headers. 108 bool vis_stack[MAX_HEADER_NESTING]; 109 int vis_i; 110 111 //! Should we show the GUI headers for record sinks. 112 bool inhibit_sink_headers; 113}; 114 115/*! 116 * State for plotting @ref m_ff_vec3_f32, assumes it's relative to now. 117 * 118 * @ingroup gui_debug 119 */ 120struct plot_state 121{ 122 //! The filter fifo we are plotting. 123 struct m_ff_vec3_f32 *ff; 124 125 //! When now is, all entries are made relative to this. 126 uint64_t now; 127}; 128 129DEBUG_GET_ONCE_BOOL_OPTION(curated_gui, "XRT_CURATED_GUI", false) 130 131 132/* 133 * 134 * Helper functions. 135 * 136 */ 137 138static void 139conv_rgb_f32_to_u8(struct xrt_colour_rgb_f32 *from, struct xrt_colour_rgb_u8 *to) 140{ 141 to->r = (uint8_t)(from->r * 255.0f); 142 to->g = (uint8_t)(from->g * 255.0f); 143 to->b = (uint8_t)(from->b * 255.0f); 144} 145 146static void 147conv_rgb_u8_to_f32(struct xrt_colour_rgb_u8 *from, struct xrt_colour_rgb_f32 *to) 148{ 149 to->r = from->r / 255.0f; 150 to->g = from->g / 255.0f; 151 to->b = from->b / 255.0f; 152} 153 154static void 155handle_draggable_vec3_f32(const char *name, struct xrt_vec3 *v) 156{ 157 float min = -256.0f; 158 float max = 256.0f; 159 160 igDragFloat3(name, (float *)v, 0.005f, min, max, "%+f", 1.0f); 161} 162 163static void 164handle_draggable_quat(const char *name, struct xrt_quat *q) 165{ 166 float min = -1.0f; 167 float max = 1.0f; 168 169 igDragFloat4(name, (float *)q, 0.005f, min, max, "%+f", 1.0f); 170 171 // Avoid invalid 172 if (q->x == 0.0f && q->y == 0.0f && q->z == 0.0f && q->w == 0.0f) { 173 q->w = 1.0f; 174 } 175 176 // And make sure it's a unit rotation. 177 math_quat_normalize(q); 178} 179 180static struct debug_record * 181ensure_debug_record_created(void *ptr, struct draw_state *state) 182{ 183 struct debug_scene *ds = state->ds; 184 struct u_sink_debug *usd = (struct u_sink_debug *)ptr; 185 186 if (usd->sink == NULL) { 187 struct debug_record *dr = &ds->recs[ds->num_recrs++]; 188 189 dr->ptr = ptr; 190 191 gui_window_record_init(&dr->rw); 192 u_sink_debug_set_sink(usd, &dr->rw.sink); 193 194 return dr; 195 } 196 197 for (size_t i = 0; i < ARRAY_SIZE(ds->recs); i++) { 198 struct debug_record *dr = &ds->recs[i]; 199 200 if ((ptrdiff_t)dr->ptr == (ptrdiff_t)ptr) { 201 return dr; 202 } 203 } 204 205 return NULL; 206} 207 208// Currently unused. 209XRT_MAYBE_UNUSED static void 210draw_sink_to_background(struct u_var_info *var, struct draw_state *state) 211{ 212 struct debug_record *dr = ensure_debug_record_created(var->ptr, state); 213 if (dr == NULL) { 214 return; 215 } 216 217 gui_window_record_to_background(&dr->rw, state->p); 218} 219 220XRT_MAYBE_UNUSED static void 221draw_native_images_to_background(struct u_var_info *var, struct draw_state *state) 222{ 223 struct debug_scene *ds = state->ds; 224 struct u_native_images_debug *unid = (struct u_native_images_debug *)var->ptr; 225 226 struct gui_widget_native_images *gwni = gui_widget_native_images_storage_ensure(&ds->gwnis, unid); 227 if (gwni == NULL) { 228 return; 229 } 230 231 gui_widget_native_images_update(gwni, unid); 232 gui_widget_native_images_to_background(gwni, state->p); 233} 234 235 236/* 237 * 238 * Plot helpers. 239 * 240 */ 241 242#define PLOT_HELPER(elm) \ 243 static void *plot_vec3_f32_##elm(void *ptr, int index, ImPlotPoint *point) \ 244 { \ 245 struct plot_state *state = (struct plot_state *)ptr; \ 246 struct xrt_vec3 value; \ 247 uint64_t timestamp; \ 248 m_ff_vec3_f32_get(state->ff, index, &value, &timestamp); \ 249 *point = (ImPlotPoint){time_ns_to_s(state->now - timestamp), value.elm}; \ 250 return NULL; \ 251 } 252 253PLOT_HELPER(x) 254PLOT_HELPER(y) 255PLOT_HELPER(z) 256 257static void * 258plot_curve_point(void *ptr, int i, ImPlotPoint *point) 259{ 260 struct u_var_curve *c = (struct u_var_curve *)ptr; 261 struct u_var_curve_point p = c->getter(c->data, i); 262 *point = (ImPlotPoint){p.x, p.y}; 263 return NULL; 264} 265 266static float 267plot_f32_array_value(void *ptr, int i) 268{ 269 float *arr = ptr; 270 return arr[i]; 271} 272 273 274/* 275 * 276 * Main debug gui visitor functions. 277 * 278 */ 279 280 281static void 282on_color_rgb_f32(const char *name, void *ptr) 283{ 284 igColorEdit3(name, (float *)ptr, COLOR_FLAGS); 285 igSameLine(0.0f, 4.0f); 286 igText("%s", name); 287} 288 289static void 290on_color_rgb_u8(const char *name, void *ptr) 291{ 292 struct xrt_colour_rgb_f32 tmp; 293 conv_rgb_u8_to_f32((struct xrt_colour_rgb_u8 *)ptr, &tmp); 294 igColorEdit3(name, (float *)&tmp, COLOR_FLAGS); 295 igSameLine(0.0f, 4.0f); 296 igText("%s", name); 297 conv_rgb_f32_to_u8(&tmp, (struct xrt_colour_rgb_u8 *)ptr); 298} 299 300static void 301on_f32_arr(const char *name, void *ptr) 302{ 303 struct u_var_f32_arr *f32_arr = ptr; 304 int index = *f32_arr->index_ptr; 305 int length = f32_arr->length; 306 float *arr = (float *)f32_arr->data; 307 308 ImVec2 min, max; 309 igGetWindowContentRegionMin(&min); 310 igGetWindowContentRegionMax(&max); 311 ImVec2 graph_size = {max.x - min.x, 200}; 312 313 float stats_min = FLT_MAX; 314 float stats_max = FLT_MAX; 315 316 igPlotLines_FnFloatPtr( // 317 name, // 318 plot_f32_array_value, // 319 arr, // 320 length, // 321 index, // 322 NULL, // 323 stats_min, // 324 stats_max, // 325 graph_size); // 326} 327 328static void 329on_timing(const char *name, void *ptr) 330{ 331 struct u_var_timing *frametime_arr = ptr; 332 struct u_var_f32_arr *f32_arr = &frametime_arr->values; 333 int index = *f32_arr->index_ptr; 334 int length = f32_arr->length; 335 float *arr = (float *)f32_arr->data; 336 337 ImVec2 min, max; 338 igGetWindowContentRegionMin(&min); 339 igGetWindowContentRegionMax(&max); 340 ImVec2 graph_size = {max.x - min.x, 200}; 341 342 float stats_min = FLT_MAX; 343 float stats_max = 0; 344 345 for (int f = 0; f < length; f++) { 346 if (arr[f] < stats_min) 347 stats_min = arr[f]; 348 if (arr[f] > stats_max) 349 stats_max = arr[f]; 350 } 351 352 igPlotTimings( // 353 name, // 354 plot_f32_array_value, // 355 arr, // 356 length, // 357 index, // 358 NULL, // 359 0, // 360 stats_max, // 361 graph_size, // 362 frametime_arr->reference_timing, // 363 frametime_arr->center_reference_timing, // 364 frametime_arr->range, // 365 frametime_arr->unit, // 366 frametime_arr->dynamic_rescale); // 367} 368 369static void 370on_pose(const char *name, void *ptr) 371{ 372 struct xrt_pose *pose = (struct xrt_pose *)ptr; 373 char text[512]; 374 snprintf(text, 512, "%s.position", name); 375 handle_draggable_vec3_f32(text, &pose->position); 376 snprintf(text, 512, "%s.orientation", name); 377 handle_draggable_quat(text, &pose->orientation); 378} 379 380static void 381on_ro_i64_ns(const char *name, void *ptr) 382{ 383 const ImGuiInputTextFlags ro_i_flags = ImGuiInputTextFlags_ReadOnly; 384 385 // Display in a more readable format. 386 double time = time_ns_to_ms_f(*(int64_t *)ptr); 387 388 igBeginGroup(); 389 igPushID_Str(name); 390 igPushMultiItemsWidths(2, igCalcItemWidth()); 391 392 igPushID_Int(0); 393 igInputScalar("", ImGuiDataType_Double, &time, NULL, NULL, "%+.3f ms", ro_i_flags); 394 igPopItemWidth(); 395 igPopID(); 396 397 igSameLine(0, 4.0f); 398 399 igPushID_Int(1); 400 igInputScalar(name, ImGuiDataType_U64, ptr, NULL, NULL, "%" PRIu64 " ns", ro_i_flags); 401 igPopItemWidth(); 402 igPopID(); 403 404 igPopID(); 405 igEndGroup(); 406} 407 408static void 409on_ff_vec3_var(struct u_var_info *info, struct gui_program *p) 410{ 411 char tmp[512]; 412 const char *name = info->name; 413 struct m_ff_vec3_f32 *ff = (struct m_ff_vec3_f32 *)info->ptr; 414 415 416 struct xrt_vec3 value = {0}; 417 418 uint64_t timestamp; 419 420 m_ff_vec3_f32_get(ff, 0, &value, &timestamp); 421 float value_arr[3] = {value.x, value.y, value.z}; 422 423 snprintf(tmp, sizeof(tmp), "%s.toggle", name); 424 igToggleButton(tmp, &info->gui.graphed); 425 igSameLine(0, 0); 426 igInputFloat3(name, value_arr, "%+f", ImGuiInputTextFlags_ReadOnly); 427 428 if (!info->gui.graphed) { 429 return; 430 } 431 432 433 /* 434 * Showing the plot 435 */ 436 437 struct plot_state state = {ff, os_monotonic_get_ns()}; 438 439 ImVec2 min, max; 440 igGetWindowContentRegionMin(&min); 441 igGetWindowContentRegionMax(&max); 442 ImVec2 size = {max.x - min.x, 256}; 443 bool shown = ImPlot_BeginPlot(name, size, 0); 444 if (!shown) { 445 return; 446 } 447 448 ImPlot_SetupAxis(ImAxis_X1, "time", 0); 449 ImPlot_SetupAxis(ImAxis_Y1, "value", 0); 450 451 size_t num = m_ff_vec3_f32_get_num(ff); 452 ImPlot_PlotLineG("z", plot_vec3_f32_z, &state, num, 0); // ZXY order to match RGB colors with default color map 453 ImPlot_PlotLineG("x", plot_vec3_f32_x, &state, num, 0); 454 ImPlot_PlotLineG("y", plot_vec3_f32_y, &state, num, 0); 455 456 ImPlot_EndPlot(); 457} 458 459static void 460on_sink_debug_var(const char *name, void *ptr, struct draw_state *state) 461{ 462 bool gui_header = !state->inhibit_sink_headers; 463 464 struct debug_record *dr = ensure_debug_record_created(ptr, state); 465 if (dr == NULL) { 466 return; 467 } 468 469 if (gui_header) { 470 const ImGuiTreeNodeFlags_ flags = ImGuiTreeNodeFlags_DefaultOpen; 471 if (!igCollapsingHeader_BoolPtr(name, NULL, flags)) { 472 return; 473 } 474 } 475 476 gui_window_record_render(&dr->rw, state->p); 477} 478 479static void 480on_native_images_debug_var(const char *name, void *ptr, struct draw_state *state) 481{ 482 bool gui_header = !state->inhibit_sink_headers; 483 484 struct debug_scene *ds = state->ds; 485 struct u_native_images_debug *unid = (struct u_native_images_debug *)ptr; 486 487 if (gui_header) { 488 const ImGuiTreeNodeFlags_ flags = ImGuiTreeNodeFlags_DefaultOpen; 489 if (!igCollapsingHeader_BoolPtr(name, NULL, flags)) { 490 return; 491 } 492 } 493 494 struct gui_widget_native_images *gwni = gui_widget_native_images_storage_ensure(&ds->gwnis, unid); 495 if (gwni == NULL) { 496 return; 497 } 498 499 gui_widget_native_images_update(gwni, unid); 500 gui_widget_native_images_render(gwni, state->p); 501} 502 503static void 504on_button_var(const char *name, void *ptr) 505{ 506 struct u_var_button *btn = (struct u_var_button *)ptr; 507 ImVec2 dims = {btn->width, btn->height}; 508 const char *label = strlen(btn->label) == 0 ? name : btn->label; 509 bool disabled = btn->disabled; 510 511 if (disabled) { 512 igPushStyleVar_Float(ImGuiStyleVar_Alpha, 0.6f); 513 igPushItemFlag(ImGuiItemFlags_Disabled, true); 514 } 515 516 if (igButton(label, dims)) { 517 btn->cb(btn->ptr); 518 } 519 520 if (disabled) { 521 igPopItemFlag(); 522 igPopStyleVar(1); 523 } 524 525 // Checking for downed. 526 if (disabled) { 527 btn->downed = false; 528 } else { 529 btn->downed = igIsItemHovered(ImGuiHoveredFlags_RectOnly) && igIsMouseDown_Nil(ImGuiMouseButton_Left) && 530 igIsItemActive(); 531 } 532} 533 534static void 535on_combo_var(const char *name, void *ptr) 536{ 537 struct u_var_combo *combo = (struct u_var_combo *)ptr; 538 igCombo_Str(name, combo->value, combo->options, combo->count); 539} 540 541static void 542on_histogram_f32_var(const char *name, void *ptr) 543{ 544 struct u_var_histogram_f32 *h = (struct u_var_histogram_f32 *)ptr; 545 ImVec2 zero = {h->width, h->height}; 546 igPlotHistogram_FloatPtr(name, h->values, h->count, 0, NULL, FLT_MAX, FLT_MAX, zero, sizeof(float)); 547} 548 549static void 550on_curve_var(const char *name, void *ptr) 551{ 552 struct u_var_curve *c = (struct u_var_curve *)ptr; 553 554 ImVec2 min, max; 555 igGetWindowContentRegionMin(&min); 556 igGetWindowContentRegionMax(&max); 557 ImVec2 size = {max.x - min.x, 256}; 558 559 bool shown = ImPlot_BeginPlot(name, size, 0); 560 if (!shown) { 561 return; 562 } 563 564 ImPlot_SetupAxis(ImAxis_X1, c->xlabel, 0); 565 ImPlot_SetupAxis(ImAxis_Y1, c->ylabel, 0); 566 567 ImPlot_PlotLineG(c->label, plot_curve_point, c, c->count, 0); 568 ImPlot_EndPlot(); 569} 570 571static void 572on_curves_var(const char *name, void *ptr) 573{ 574 struct u_var_curves *cs = (struct u_var_curves *)ptr; 575 ImVec2 min, max; 576 igGetWindowContentRegionMin(&min); 577 igGetWindowContentRegionMax(&max); 578 ImVec2 size = {max.x - min.x, 256}; 579 580 bool shown = ImPlot_BeginPlot(name, size, 0); 581 if (!shown) { 582 return; 583 } 584 585 ImPlot_SetupAxis(ImAxis_X1, cs->xlabel, 0); 586 ImPlot_SetupAxis(ImAxis_Y1, cs->ylabel, 0); 587 588 for (int i = 0; i < cs->curve_count; i++) { 589 struct u_var_curve *c = &cs->curves[i]; 590 ImPlot_PlotLineG(c->label, plot_curve_point, c, c->count, 0); 591 } 592 ImPlot_EndPlot(); 593} 594 595static void 596on_draggable_f32_var(const char *name, void *ptr) 597{ 598 struct u_var_draggable_f32 *d = (struct u_var_draggable_f32 *)ptr; 599 igDragFloat(name, &d->val, d->step, d->min, d->max, "%+f", ImGuiSliderFlags_None); 600} 601 602static void 603on_draggable_u16_var(const char *name, void *ptr) 604{ 605 struct u_var_draggable_u16 *d = (struct u_var_draggable_u16 *)ptr; 606 igDragScalar(name, ImGuiDataType_U16, d->val, d->step, &d->min, &d->max, NULL, ImGuiSliderFlags_None); 607} 608 609static void 610on_gui_header(const char *name, struct draw_state *state) 611{ 612 613 assert(state->vis_i == 0 && "Do not mix GUI_HEADER with GUI_HEADER_BEGIN/END"); 614 state->vis_stack[state->vis_i] = igCollapsingHeader_BoolPtr(name, NULL, 0); 615} 616 617static void 618on_gui_header_begin(const char *name, struct draw_state *state) 619{ 620 bool is_open = igCollapsingHeader_BoolPtr(name, NULL, 0); 621 state->vis_stack[state->vis_i] = is_open; 622 if (is_open) { 623 igIndent(8.0f); 624 } 625} 626 627static void 628on_gui_header_end(void) 629{ 630 igDummy((ImVec2){0, 8.0f}); 631 igUnindent(8.0f); 632} 633 634static void 635on_root_enter(struct u_var_root_info *info, void *priv) 636{ 637 struct draw_state *state = (struct draw_state *)priv; 638 state->vis_i = 0; 639 state->vis_stack[0] = true; 640 641 igBegin(info->name, NULL, 0); 642} 643 644static void 645on_elem(struct u_var_info *info, void *priv) 646{ 647 const char *name = info->name; 648 void *ptr = info->ptr; 649 enum u_var_kind kind = info->kind; 650 651 struct draw_state *state = (struct draw_state *)priv; 652 653 bool visible = state->vis_stack[state->vis_i]; 654 655 // Handle the visibility stack. 656 switch (kind) { 657 case U_VAR_KIND_GUI_HEADER_BEGIN: // Increment stack and copy the current visible stack. 658 state->vis_i++; 659 state->vis_stack[state->vis_i] = visible; 660 break; 661 case U_VAR_KIND_GUI_HEADER_END: // Decrement the stack. 662 state->vis_i--; 663 break; 664 case U_VAR_KIND_GUI_HEADER: // Always visible. 665 on_gui_header(name, state); 666 return; // Not doing anything more. 667 default: break; 668 } 669 670 // Check balanced GUI_HEADER_BEGIN/END pairs 671 assert(state->vis_i >= 0 && state->vis_i < MAX_HEADER_NESTING); 672 673 if (!visible) { 674 return; 675 } 676 677 const float drag_speed = 0.2f; 678 const float power = 1.0f; 679 ImGuiInputTextFlags i_flags = ImGuiInputTextFlags_None; 680 ImGuiInputTextFlags ro_i_flags = ImGuiInputTextFlags_ReadOnly; 681 682 switch (kind) { 683 case U_VAR_KIND_BOOL: igCheckbox(name, (bool *)ptr); break; 684 case U_VAR_KIND_RGB_F32: on_color_rgb_f32(name, ptr); break; 685 case U_VAR_KIND_RGB_U8: on_color_rgb_u8(name, ptr); break; 686 case U_VAR_KIND_U8: igDragScalar(name, ImGuiDataType_U8, ptr, drag_speed, NULL, NULL, NULL, power); break; 687 case U_VAR_KIND_U16: igDragScalar(name, ImGuiDataType_U16, ptr, drag_speed, NULL, NULL, NULL, power); break; 688 case U_VAR_KIND_U64: igDragScalar(name, ImGuiDataType_U64, ptr, drag_speed, NULL, NULL, NULL, power); break; 689 case U_VAR_KIND_I32: igInputInt(name, (int *)ptr, 1, 10, i_flags); break; 690 case U_VAR_KIND_I64: igInputScalar(name, ImGuiDataType_S64, ptr, NULL, NULL, NULL, i_flags); break; 691 case U_VAR_KIND_VEC3_I32: igInputInt3(name, (int *)ptr, i_flags); break; 692 case U_VAR_KIND_F32: igInputFloat(name, (float *)ptr, 1, 10, "%+f", i_flags); break; 693 case U_VAR_KIND_F64: igInputDouble(name, (double *)ptr, 0.1, 1, "%+f", i_flags); break; 694 case U_VAR_KIND_F32_ARR: on_f32_arr(name, ptr); break; 695 case U_VAR_KIND_TIMING: on_timing(name, ptr); break; 696 case U_VAR_KIND_VEC3_F32: igInputFloat3(name, (float *)ptr, "%+f", i_flags); break; 697 case U_VAR_KIND_POSE: on_pose(name, ptr); break; 698 case U_VAR_KIND_LOG_LEVEL: igCombo_Str(name, (int *)ptr, "Trace\0Debug\0Info\0Warn\0Error\0\0", 5); break; 699 case U_VAR_KIND_RO_TEXT: igText("%s: '%s'", name, (char *)ptr); break; 700 case U_VAR_KIND_RO_RAW_TEXT: igText("%s", (char *)ptr); break; 701 case U_VAR_KIND_RO_I16: igInputScalar(name, ImGuiDataType_S16, ptr, NULL, NULL, NULL, ro_i_flags); break; 702 case U_VAR_KIND_RO_I32: igInputScalar(name, ImGuiDataType_S32, ptr, NULL, NULL, NULL, ro_i_flags); break; 703 case U_VAR_KIND_RO_U16: igInputScalar(name, ImGuiDataType_U16, ptr, NULL, NULL, NULL, ro_i_flags); break; 704 case U_VAR_KIND_RO_U32: igInputScalar(name, ImGuiDataType_U32, ptr, NULL, NULL, NULL, ro_i_flags); break; 705 case U_VAR_KIND_RO_F32: igInputScalar(name, ImGuiDataType_Float, ptr, NULL, NULL, "%+f", ro_i_flags); break; 706 case U_VAR_KIND_RO_I64: igInputScalar(name, ImGuiDataType_S64, ptr, NULL, NULL, NULL, ro_i_flags); break; 707 case U_VAR_KIND_RO_U64: igInputScalar(name, ImGuiDataType_S64, ptr, NULL, NULL, NULL, ro_i_flags); break; 708 case U_VAR_KIND_RO_F64: igInputScalar(name, ImGuiDataType_Double, ptr, NULL, NULL, "%+f", ro_i_flags); break; 709 case U_VAR_KIND_RO_I64_NS: on_ro_i64_ns(name, ptr); break; 710 case U_VAR_KIND_RO_VEC3_I32: igInputInt3(name, (int *)ptr, ro_i_flags); break; 711 case U_VAR_KIND_RO_VEC3_F32: igInputFloat3(name, (float *)ptr, "%+f", ro_i_flags); break; 712 case U_VAR_KIND_RO_QUAT_F32: igInputFloat4(name, (float *)ptr, "%+f", ro_i_flags); break; 713 case U_VAR_KIND_RO_FF_VEC3_F32: on_ff_vec3_var(info, state->p); break; 714 case U_VAR_KIND_GUI_HEADER: assert(false && "Should be handled before this"); break; 715 case U_VAR_KIND_GUI_HEADER_BEGIN: on_gui_header_begin(name, state); break; 716 case U_VAR_KIND_GUI_HEADER_END: on_gui_header_end(); break; 717 case U_VAR_KIND_GUI_SAMELINE: igSameLine(0.0, 4.0f); break; 718 case U_VAR_KIND_SINK_DEBUG: on_sink_debug_var(name, ptr, state); break; 719 case U_VAR_KIND_NATIVE_IMAGES_DEBUG: on_native_images_debug_var(name, ptr, state); break; 720 case U_VAR_KIND_DRAGGABLE_F32: on_draggable_f32_var(name, ptr); break; 721 case U_VAR_KIND_BUTTON: on_button_var(name, ptr); break; 722 case U_VAR_KIND_COMBO: on_combo_var(name, ptr); break; 723 case U_VAR_KIND_DRAGGABLE_U16: on_draggable_u16_var(name, ptr); break; 724 case U_VAR_KIND_HISTOGRAM_F32: on_histogram_f32_var(name, ptr); break; 725 case U_VAR_KIND_CURVE: on_curve_var(name, ptr); break; 726 case U_VAR_KIND_CURVES: on_curves_var(name, ptr); break; 727 default: igLabelText(name, "Unknown tag '%i'", kind); break; 728 } 729} 730 731static void 732on_root_exit(struct u_var_root_info *info, void *priv) 733{ 734 struct draw_state *state = (struct draw_state *)priv; 735 assert(state->vis_i == 0 && "Unbalanced GUI_HEADER_BEGIN/END pairs"); 736 state->vis_i = 0; 737 state->vis_stack[0] = false; 738 739 igEnd(); 740} 741 742 743/* 744 * 745 * Advanced UI. 746 * 747 */ 748 749static bool g_show_advanced_gui = false; 750 751static void 752advanced_scene_render(struct debug_scene *ds, struct gui_program *p) 753{ 754 struct draw_state state = {p, ds, {0}, 0, false}; 755 756 u_var_visit(on_root_enter, on_root_exit, on_elem, &state); 757 758 igBegin("Advanced UI", NULL, 0); 759 igCheckbox("Show advanced UI", &g_show_advanced_gui); 760 igEnd(); 761} 762 763 764/* 765 * 766 * Curated UI. 767 * 768 */ 769 770/*! 771 * Which window are we searching. 772 */ 773enum search_type 774{ 775 SEARCH_INVALID, 776 SEARCH_GUI_CONTROL, 777 SEARCH_IPC_SERVER, 778 SEARCH_SPACE_OVERSEER, 779 SEARCH_SLAM_TRACKER, 780 SEARCH_READBACK, 781 SEARCH_HAND_TRACKER, 782 SEARCH_APP_TIMING, 783 SEARCH_COMPOSITOR, 784 SEARCH_COMPOSITOR_TIMING, 785}; 786 787/*! 788 * Extra state for curated debug UI. 789 */ 790struct curated_state 791{ 792 //! Has the be first. 793 struct draw_state ds; 794 795 enum search_type search; 796 797 struct 798 { 799 struct u_var_info *sink; 800 struct u_var_info *enable; 801 } readback; //!< Compositor readback variables. 802 803 struct 804 { 805 struct u_var_info *view_0; 806 struct u_var_info *enable; 807 } mirror; //!< Compositor mirror variables. 808 809 struct 810 { 811 struct 812 { 813 struct u_var_info *min; 814 } apps[4]; 815 uint32_t app_count; 816 817 struct u_var_info *present_to_display; 818 } timing; //!< Both compositor and app timing related things. 819 820 struct 821 { 822 struct u_var_info *size; 823 struct u_var_info *detect; 824 struct u_var_info *cams; 825 struct u_var_info *graph; 826 } hand_tracking; 827 828 struct u_var_info *clear; 829 830 struct u_var_info *ipc_running; 831}; 832 833#define CHECK_RAW(NAME, TYPE) \ 834 if (strcmp(info->raw_name, NAME) == 0) { \ 835 cs->search = TYPE; \ 836 } 837 838#define CHECK(NAME, FIELD) \ 839 if (strcmp(info->name, NAME) == 0) { \ 840 cs->FIELD = info; \ 841 } 842 843#define DRAW(FIELD) \ 844 if (cs.FIELD != NULL) { \ 845 on_elem(cs.FIELD, &cs.ds); \ 846 } 847 848static void 849curated_on_root_enter(struct u_var_root_info *info, void *priv) 850{ 851 struct curated_state *cs = (struct curated_state *)priv; 852 853 CHECK_RAW("GUI Control", SEARCH_GUI_CONTROL); 854 CHECK_RAW("IPC Server", SEARCH_IPC_SERVER); 855 CHECK_RAW("Tracking Factory", SEARCH_INVALID); 856 CHECK_RAW("Space Overseer", SEARCH_SPACE_OVERSEER); 857 CHECK_RAW("Prober", SEARCH_INVALID); 858 CHECK_RAW("SLAM Tracker", SEARCH_SLAM_TRACKER); 859 CHECK_RAW("Vive Device", SEARCH_INVALID); 860 CHECK_RAW("V4L2 Frameserver", SEARCH_INVALID); 861 CHECK_RAW("Hand-tracking async shim!", SEARCH_INVALID); 862 CHECK_RAW("Controller emulation!", SEARCH_INVALID); 863 CHECK_RAW("Camera-based Hand Tracker", SEARCH_HAND_TRACKER); 864 CHECK_RAW("App timing info", SEARCH_APP_TIMING); 865 CHECK_RAW("Compositor", SEARCH_COMPOSITOR); 866 CHECK_RAW("Compositor timing info", SEARCH_COMPOSITOR_TIMING); 867 CHECK_RAW("Readback", SEARCH_READBACK); 868 869 // If we have too many app timing structs, ignore them. 870 if (cs->search == SEARCH_APP_TIMING && cs->timing.app_count >= ARRAY_SIZE(cs->timing.apps)) { 871 cs->search = SEARCH_INVALID; 872 } 873} 874 875static void 876curated_on_elem(struct u_var_info *info, void *priv) 877{ 878 struct curated_state *cs = (struct curated_state *)priv; 879 880 switch (cs->search) { 881 case SEARCH_INVALID: // Invalid 882 break; 883 case SEARCH_GUI_CONTROL: // GUI Control 884 CHECK("Clear Colour", clear); 885 break; 886 case SEARCH_READBACK: // Readback 887 CHECK("Readback left eye to debug GUI", readback.enable) 888 CHECK("Left view!", readback.sink) 889 break; 890 case SEARCH_HAND_TRACKER: // Camera-based Hand Tracker 891 CHECK("Hand size (Meters between wrist and middle-proximal joint)", hand_tracking.size) 892 CHECK("Estimate hand sizes", hand_tracking.detect) 893 CHECK("Annotated camera feeds", hand_tracking.cams) 894 CHECK("Model inputs and outputs", hand_tracking.graph) 895 break; 896 case SEARCH_COMPOSITOR: // Compositor main. 897 CHECK("Debug: Disable fast path", mirror.enable) 898 CHECK("View[0]", mirror.view_0) 899 break; 900 case SEARCH_COMPOSITOR_TIMING: // Compositor timing info 901 CHECK("Present to display offset(ms)", timing.present_to_display) 902 break; 903 case SEARCH_IPC_SERVER: // IPC Server 904 CHECK("running", ipc_running) 905 break; 906 case SEARCH_APP_TIMING: // App timing info 907 // App count is incremented on root exit, so app_count is the current one. 908 CHECK("Minimum app time(ms)", timing.apps[cs->timing.app_count].min) 909 break; 910 case SEARCH_SPACE_OVERSEER: 911 // Nothing yet. 912 break; 913 case SEARCH_SLAM_TRACKER: 914 // Nothing yet. 915 break; 916 } 917} 918 919static void 920curated_on_root_exit(struct u_var_root_info *info, void *priv) 921{ 922 struct curated_state *cs = (struct curated_state *)priv; 923 924 if (cs->search == SEARCH_APP_TIMING) { 925 cs->timing.app_count++; 926 } 927} 928 929static void 930curated_render(struct debug_scene *ds, struct gui_program *p) 931{ 932 struct curated_state cs = XRT_STRUCT_INIT; 933 cs.ds = (struct draw_state){p, ds, {0}, 0, false}; 934 cs.ds.vis_stack[0] = true; // Make sure things are visible. 935 936 // Collect the variables. 937 u_var_visit(curated_on_root_enter, curated_on_root_exit, curated_on_elem, &cs); 938 939 // Always enable the mirror sink. 940 if (cs.mirror.enable != NULL) { 941 *(bool *)cs.mirror.enable->ptr = true; 942 } 943 944 // Always set the clear colour. 945 if (cs.clear != NULL) { 946 *(struct xrt_colour_rgb_f32 *)cs.clear->ptr = (struct xrt_colour_rgb_f32){0.f, 0.f, 0.f}; 947 } 948 949 // Start drawing. 950 igBegin("Monado", NULL, 0); 951 952 // Top exit button. 953 ImVec2 button_dims = {48, 24}; 954 igSameLine(igGetWindowWidth() - button_dims.x - 8, -1); 955 if (igButton("Exit", button_dims) && cs.ipc_running != NULL) { 956 *(bool *)cs.ipc_running->ptr = false; 957 } 958 959 ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; 960 if (igBeginTabBar("Tabs", tab_bar_flags)) { 961 if (igBeginTabItem("Main", NULL, 0)) { 962 DRAW(ipc_running); 963 igCheckbox("Show advanced UI", &g_show_advanced_gui); 964 igEndTabItem(); 965 } 966 967 if (igBeginTabItem("Timing", NULL, 0)) { 968 for (uint32_t i = 0; i < cs.timing.app_count; i++) { 969 igText("App %u", i + 1); 970 DRAW(timing.apps[i].min); 971 } 972 973 if (cs.timing.present_to_display != NULL) { 974 igText("Compositor"); 975 DRAW(timing.present_to_display); 976 } 977 978 igEndTabItem(); 979 } 980 981 // Close tab bar. 982 igEndTabBar(); 983 } 984 985 // If available draw the mirror native images the background. 986 if (cs.mirror.view_0 != NULL) { 987 draw_native_images_to_background(cs.mirror.view_0, &cs.ds); 988 } 989 990 igEnd(); 991} 992 993 994/* 995 * 996 * Sink interception. 997 * 998 */ 999 1000static void 1001on_root_enter_sink(struct u_var_root_info *info, void *priv) 1002{} 1003 1004static void 1005on_elem_sink_debug_remove(struct u_var_info *info, void *null_ptr) 1006{ 1007 void *ptr = info->ptr; 1008 enum u_var_kind kind = info->kind; 1009 1010 if (kind != U_VAR_KIND_SINK_DEBUG) { 1011 return; 1012 } 1013 1014 struct u_sink_debug *usd = (struct u_sink_debug *)ptr; 1015 u_sink_debug_set_sink(usd, NULL); 1016} 1017 1018static void 1019on_root_exit_sink(struct u_var_root_info *info, void *priv) 1020{} 1021 1022 1023/* 1024 * 1025 * Scene functions. 1026 * 1027 */ 1028 1029static void 1030scene_render(struct gui_scene *scene, struct gui_program *p) 1031{ 1032 struct debug_scene *ds = (struct debug_scene *)scene; 1033 1034 static bool first = true; 1035 if (first) { 1036 g_show_advanced_gui = !debug_get_bool_option_curated_gui(); 1037 first = false; 1038 } 1039 1040 if (g_show_advanced_gui) { 1041 advanced_scene_render(ds, p); 1042 } else { 1043 curated_render(ds, p); 1044 } 1045} 1046 1047static void 1048scene_destroy(struct gui_scene *scene, struct gui_program *p) 1049{ 1050 struct debug_scene *ds = (struct debug_scene *)scene; 1051 1052 // Remove the sink interceptors. 1053 u_var_visit(on_root_enter_sink, on_root_exit_sink, on_elem_sink_debug_remove, NULL); 1054 1055 if (ds->xfctx != NULL) { 1056 xrt_frame_context_destroy_nodes(ds->xfctx); 1057 ds->xfctx = NULL; 1058 } 1059 1060 free(ds); 1061} 1062 1063 1064/* 1065 * 1066 * 'Exported' functions. 1067 * 1068 */ 1069 1070void 1071gui_scene_debug(struct gui_program *p) 1072{ 1073 struct debug_scene *ds = U_TYPED_CALLOC(struct debug_scene); 1074 1075 ds->base.render = scene_render; 1076 ds->base.destroy = scene_destroy; 1077 1078 gui_scene_push_front(p, &ds->base); 1079}