ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

Commit: 8abf3591606238d6cff40e3e64163174f8e69b38
Parent: 68267f952526109e768e4c350626783d1b9d190c
Author: Randy Palamar
Date:   Mon, 23 Jun 2025 06:22:42 -0600

ui: add compute stats bar graph mode

Diffstat:
Mui.c | 254++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 195 insertions(+), 59 deletions(-)

diff --git a/ui.c b/ui.c @@ -14,7 +14,6 @@ * [ ]: refactor: add_variable_no_link() * [ ]: refactor: draw_text_limited should clamp to rect and measure text itself * [ ]: ui leaks split beamform views on hot-reload - * [ ]: add tag based selection to frame views * [ ]: draw the ui with a post-order traversal instead of pre-order traversal * [ ]: consider V_HOVER_GROUP and use that to implement submenus * [ ]: menu's need to support nested groups @@ -26,6 +25,7 @@ * [ ]: visual indicator for broken shader stage gh#27 * [ ]: bug: cross-plane view with different dimensions for each plane * [ ]: refactor: make table_skip_rows useful + * [ ]: refactor: better method of grouping variables for views such as FrameView/ComputeStatsView */ #define BG_COLOUR (v4){.r = 0.15, .g = 0.12, .b = 0.13, .a = 1.0} @@ -37,6 +37,15 @@ #define MENU_PLUS_COLOUR (v4){.r = 0.33, .g = 0.42, .b = 1.00, .a = 1.0} #define MENU_CLOSE_COLOUR FOCUSED_COLOUR +read_only global v4 g_colour_palette[] = { + {{0.32, 0.20, 0.50, 1.00}}, + {{0.14, 0.39, 0.61, 1.00}}, + {{0.61, 0.14, 0.25, 1.00}}, + {{0.20, 0.60, 0.24, 1.00}}, + {{0.80, 0.60, 0.20, 1.00}}, + {{0.15, 0.51, 0.74, 1.00}}, +}; + #define HOVER_SPEED 5.0f #define TABLE_CELL_PAD_HEIGHT 2.0f @@ -111,10 +120,19 @@ typedef struct { RegionSplitDirection direction; } RegionSplit; +#define COMPUTE_STATS_VIEW_LIST \ + X(Average, "Average") \ + X(Bar, "Bar") + +#define X(kind, ...) ComputeStatsViewKind_ ##kind, +typedef enum {COMPUTE_STATS_VIEW_LIST ComputeStatsViewKind_Count} ComputeStatsViewKind; +#undef X + /* TODO(rnp): this should be refactored to not need a BeamformerCtx */ typedef struct { BeamformerCtx *ctx; - void *stats; + Variable *cycler; + ComputeStatsViewKind kind; } ComputeStatsView; typedef struct { @@ -220,6 +238,7 @@ struct Variable { void *generic; BeamformerVariable beamformer_variable; ComputeProgressBar compute_progress_bar; + ComputeStatsView compute_stats_view; RegionSplit region_split; ScaleBar scale_bar; UIButtonID button; @@ -882,14 +901,20 @@ add_ui_split(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, f32 frac } function Variable * -add_global_menu(BeamformerUI *ui, Arena *arena, Variable *parent) +add_global_menu_to_group(BeamformerUI *ui, Arena *arena, Variable *group) { - Variable *result = add_variable_group(ui, 0, &ui->arena, s8(""), VariableGroupKind_List, ui->small_font); - result->parent = parent; - #define X(id, text) add_button(ui, result, &ui->arena, s8(text), UI_BID_ ##id, 0, ui->small_font); + #define X(id, text) add_button(ui, group, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); GLOBAL_MENU_BUTTONS #undef X - return result; + return group; +} + +function Variable * +add_global_menu(BeamformerUI *ui, Arena *arena, Variable *parent) +{ + Variable *result = add_variable_group(ui, 0, arena, s8(""), VariableGroupKind_List, ui->small_font); + result->parent = parent; + return add_global_menu_to_group(ui, arena, result); } function Variable * @@ -1048,12 +1073,9 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, lateral->zoom_starting_coord = F32_INFINITY; axial->zoom_starting_coord = F32_INFINITY; - Variable *menu = result->view.menu; - /* TODO(rnp): push to head of list? */ - Variable *old_menu_first = menu->group.first; - Variable *old_menu_last = menu->group.last; - menu->group.first = menu->group.last = 0; - + Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""), + VariableGroupKind_List, ui->small_font); + menu->parent = result; #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); FRAME_VIEW_BUTTONS #undef X @@ -1063,12 +1085,12 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, #define X(_type, _id, pretty) s8_comp(pretty), read_only local_persist s8 labels[] = {IMAGE_PLANE_TAGS s8_comp("Any")}; #undef X - bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Live: "), + bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Live:"), &bv->cycler_state, labels, countof(labels)); bv->cycler_state = IPT_LAST; } break; case FVT_INDEXED: { - bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Index: "), + bv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Index:"), &bv->cycler_state, 0, MAX_BEAMFORMED_SAVED_FRAMES); } break; default: break; @@ -1081,10 +1103,7 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); - - menu->group.last->next = old_menu_first; - menu->group.last = old_menu_last; - + add_global_menu_to_group(ui, arena, menu); return result; } @@ -1104,11 +1123,27 @@ add_compute_progress_bar(Variable *parent, BeamformerCtx *ctx) } function Variable * -add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, VariableType type) +add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, BeamformerCtx *ctx) { /* TODO(rnp): this can be closable once we have a way of opening new views */ - Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 1, 0); - result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, type, ui->small_font); + Variable *result = add_ui_view(ui, parent, arena, s8(""), UIViewFlag_CustomText, 0, 0); + result->view.child = add_variable(ui, result, &ui->arena, s8(""), 0, + VT_COMPUTE_STATS_VIEW, ui->small_font); + + Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""), + VariableGroupKind_List, ui->small_font); + menu->parent = result; + + #define X(_k, label) s8_comp(label), + read_only local_persist s8 labels[] = {COMPUTE_STATS_VIEW_LIST}; + #undef X + + ComputeStatsView *csv = &result->view.child->compute_stats_view; + /* TODO(rnp): refactor to not need the beamformer ctx */ + csv->ctx = ctx; + csv->cycler = add_variable_cycler(ui, menu, arena, 0, ui->small_font, s8("Stats View:"), + &csv->kind, labels, countof(labels)); + add_global_menu_to_group(ui, arena, menu); return result; } @@ -1318,7 +1353,10 @@ function s8 push_custom_view_title(Stream *s, Variable *var) { switch (var->type) { - case VT_COMPUTE_STATS_VIEW:{ stream_append_s8(s, s8("Compute Stats (Average)")); } break; + case VT_COMPUTE_STATS_VIEW:{ + stream_append_s8(s, s8("Compute Stats: ")); + stream_append_variable(s, var->compute_stats_view.cycler); + }break; case VT_COMPUTE_PROGRESS_BAR:{ stream_append_s8(s, s8("Compute Progress: ")); stream_append_f64(s, 100 * *var->compute_progress_bar.progress, 100); @@ -2029,54 +2067,154 @@ draw_compute_progress_bar(BeamformerUI *ui, Arena arena, ComputeProgressBar *sta return result; } +function s8 +push_compute_time(Arena *arena, s8 prefix, f32 time) +{ + Stream sb = arena_stream(*arena); + stream_append_s8(&sb, prefix); + stream_append_f64_e(&sb, time); + return arena_stream_commit(arena, &sb); +} + function v2 -draw_compute_stats_view(BeamformerCtx *ctx, Arena arena, Rect r) +draw_compute_stats_bar_view(BeamformerUI *ui, Arena arena, ComputeShaderStats *stats, u32 *stages, + u32 stages_count, f32 compute_time_sum, TextSpec ts, Rect r, v2 mouse) { - #define X(e, n, s, h, pn) [BeamformerShaderKind_##e] = s8_comp(pn ":"), + read_only local_persist s8 frame_labels[] = {s8_comp("0:"), s8_comp("-1:"), s8_comp("-2:"), s8_comp("-3:")}; + f32 total_times[countof(frame_labels)] = {0}; + Table *table = table_new(&arena, countof(frame_labels), TextAlignment_Right, TextAlignment_Left); + for (u32 i = 0; i < countof(frame_labels); i++) { + TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; + cells[0].text = frame_labels[i]; + u32 frame_index = (stats->latest_frame_index - i) % countof(stats->table.times); + u32 seen_shaders = 0; + for (u32 j = 0; j < stages_count; j++) { + if ((seen_shaders & (1 << stages[j])) == 0) + total_times[i] += stats->table.times[frame_index][stages[j]]; + seen_shaders |= (1 << stages[j]); + } + } + + #define X(e, n, s, h, pn) [BeamformerShaderKind_##e] = s8_comp(pn ": "), read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS}; #undef X - BeamformerSharedMemory *sm = ctx->shared_memory.region; - ComputeShaderStats *stats = ctx->compute_shader_stats; - BeamformerUI *ui = ctx->ui; + v2 result = table_extent(table, arena, ts.font); + + f32 remaining_width = r.size.w - result.w - table->cell_pad.w; + f32 average_width = 0.8 * remaining_width; + + s8 mouse_text = s8(""); + v2 text_pos; + + u32 row_index = 0; + TableIterator *it = table_iterator_new(table, TIK_ROWS, &arena, 0, r.pos, ts.font); + for (TableRow *row = table_iterator_next(it, &arena); + row; + row = table_iterator_next(it, &arena)) + { + Rect cr = it->cell_rect; + cr.size.w = table->widths[0]; + ts.limits.size.w = cr.size.w; + draw_table_cell(ui, arena, (TableCell *)row->data, cr, table->alignment[0], ts, mouse); + + u32 frame_index = (stats->latest_frame_index - row_index) % countof(stats->table.times); + f32 total_width = average_width * total_times[row_index] / compute_time_sum; + Rect rect; + rect.pos = add_v2(cr.pos, (v2){.x = cr.size.w + table->cell_pad.w , .y = cr.size.h * 0.15}); + rect.size = (v2){.y = 0.7 * cr.size.h}; + for (u32 i = 0; i < stages_count; i++) { + rect.size.w = total_width * stats->table.times[frame_index][stages[i]] / total_times[row_index]; + Color color = colour_from_normalized(g_colour_palette[stages[i] % countof(g_colour_palette)]); + DrawRectangleRec(rect.rl, color); + if (point_in_rect(mouse, rect)) { + text_pos = add_v2(rect.pos, (v2){.x = table->cell_pad.w}); + mouse_text = push_compute_time(&arena, labels[stages[i]], + stats->table.times[frame_index][stages[i]]); + } + rect.pos.x += rect.size.w; + } + row_index++; + } + + v2 start = add_v2(r.pos, (v2){.x = table->widths[0] + average_width + table->cell_pad.w}); + v2 end = add_v2(start, (v2){.y = result.y}); + DrawLineEx(start.rl, end.rl, 4, colour_from_normalized(FG_COLOUR)); + + if (mouse_text.len) { + ts.font = &ui->small_font; + ts.flags &= ~TF_LIMITED; + ts.flags |= TF_OUTLINED; + ts.outline_colour = (v4){.a = 1}; + ts.outline_thick = 1; + draw_text(mouse_text, text_pos, &ts); + } + + return result; +} + +function void +push_table_time_row(Table *table, Arena *arena, s8 label, f32 time) +{ + assert(table->columns == 3); + TableCell *cells = table_push_row(table, arena, TRK_CELLS)->data; + cells[0].text = label; + cells[1].text = push_compute_time(arena, s8(""), time); + cells[2].text = s8("[s]"); +} + +function v2 +draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v2 mouse) +{ + assert(view->type == VT_COMPUTE_STATS_VIEW); + + ComputeStatsView *csv = &view->compute_stats_view; + BeamformerSharedMemory *sm = csv->ctx->shared_memory.region; + ComputeShaderStats *stats = csv->ctx->compute_shader_stats; f32 compute_time_sum = 0; u32 stages = sm->compute_stages_count; TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; - static_assert(countof(labels) <= 32, "shader kind bitfield test"); + u32 compute_stages[MAX_COMPUTE_SHADER_STAGES]; + mem_copy(compute_stages, sm->compute_stages, stages * sizeof(*compute_stages)); + + static_assert(BeamformerShaderKind_ComputeCount <= 32, "shader kind bitfield test"); u32 seen_shaders = 0; - Table *table = table_new(&arena, stages + 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); for (u32 i = 0; i < stages; i++) { - TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; - - Stream sb = arena_stream(arena); - BeamformerShaderKind index = sm->compute_stages[i]; - if ((seen_shaders & (1 << index)) == 0) { + BeamformerShaderKind index = compute_stages[i]; + if ((seen_shaders & (1 << index)) == 0) compute_time_sum += stats->average_times[index]; - stream_append_f64_e(&sb, stats->average_times[index]); - seen_shaders |= (1 << index); - cells[0].text = labels[index]; - cells[1].text = arena_stream_commit(&arena, &sb); - cells[2].text = s8("[s]"); - } + seen_shaders |= (1 << index); } - TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; - Stream sb = arena_stream(arena); - stream_append_f64_e(&sb, compute_time_sum); - cells[0].text = s8("Compute Total:"); - cells[1].text = arena_stream_commit(&arena, &sb); - cells[2].text = s8("[s]"); + v2 result = {0}; - cells = table_push_row(table, &arena, TRK_CELLS)->data; - sb = arena_stream(arena); - stream_append_f64_e(&sb, stats->rf_time_delta_average); - cells[0].text = s8("RF Upload Delta:"); - cells[1].text = arena_stream_commit(&arena, &sb); - cells[2].text = s8("[s]"); + Table *table = table_new(&arena, 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); + switch (csv->kind) { + case ComputeStatsViewKind_Average:{ + #define X(e, n, s, h, pn) [BeamformerShaderKind_##e] = s8_comp(pn ":"), + read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS}; + #undef X + da_reserve(&arena, table, stages); + for (u32 i = 0; i < stages; i++) { + push_table_time_row(table, &arena, labels[compute_stages[i]], + stats->average_times[compute_stages[i]]); + } + }break; + case ComputeStatsViewKind_Bar:{ + result = draw_compute_stats_bar_view(ui, arena, stats, compute_stages, stages, compute_time_sum, + text_spec, r, mouse); + r.pos = add_v2(r.pos, (v2){.y = result.y}); + }break; + InvalidDefaultCase; + } + + push_table_time_row(table, &arena, s8("Compute Total:"), compute_time_sum); + push_table_time_row(table, &arena, s8("RF Upload Delta:"), stats->rf_time_delta_average); - table_extent(table, arena, text_spec.font); - return draw_table(ui, arena, table, r, text_spec, (v2){0}, 0); + result = add_v2(result, table_extent(table, arena, text_spec.font)); + draw_table(ui, arena, table, r, text_spec, (v2){0}, 0); + return result; } struct variable_iterator { Variable *current; }; @@ -2272,7 +2410,7 @@ draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec tex case VT_COMPUTE_PROGRESS_BAR: { size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r); } break; - case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(var->generic, ui->arena, r); }break; + case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(ui, ui->arena, var, r, mouse); }break; InvalidDefaultCase; } @@ -2969,9 +3107,7 @@ ui_init(BeamformerCtx *ctx, Arena store) split = split->region_split.right; split->region_split.left = add_compute_progress_bar(split, ctx); - split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, VT_COMPUTE_STATS_VIEW); - /* TODO(rnp): refactor to not need the beamformer ctx */ - split->region_split.right->group.first->generic = ctx; + split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, ctx); ctx->ui_read_params = 1;