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 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, ×tamp); \
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, ×tamp);
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}