ogl_beamforming

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

Commit: de13041fc7b016d02ca216295a19a10c931a9c82
Parent: fe1a4408d3e30269ff2ae6f8d0a9a4879beb350e
Author: Randy Palamar
Date:   Wed,  4 Sep 2024 12:22:53 -0600

ui: don't shoehorn every listing through the same loop

Technically I could keep the modifiable elements grouped and loop
over them but I want to be able to add buttons and they will need
a hover_t but not the other parameters. This may require slightly
more code but it keeps things clear.

This also fixes the issue with scrolling on a selected field and
scrolling the dB value causing a recompute of the output.

Diffstat:
Mbeamformer.h | 9++++++++-
Mmain.c | 1-
Mui.c | 265+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
3 files changed, 166 insertions(+), 109 deletions(-)

diff --git a/beamformer.h b/beamformer.h @@ -56,9 +56,16 @@ enum gl_vendor_ids { }; typedef struct { + f32 *value; + v2 limits; + f32 scale; + b32 compute; +} BPModifiableValue; + +typedef struct { char buf[64]; + BPModifiableValue store; i32 buf_len; - i32 idx; i32 cursor; f32 cursor_hover_p; f32 cursor_blink_t; diff --git a/main.c b/main.c @@ -169,7 +169,6 @@ main(void) ctx.font_spacing = 0; ctx.font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", ctx.font_size, 0, 0); - ctx.is.idx = -1; ctx.is.cursor_blink_t = 1; init_fragment_shader_ctx(&ctx.fsctx, ctx.out_data_dim); diff --git a/ui.c b/ui.c @@ -48,7 +48,7 @@ measure_text(Font font, s8 text) return result; } -static void +static v2 draw_text(Font font, s8 text, v2 pos, f32 rotation, Color colour) { rlPushMatrix(); @@ -79,6 +79,15 @@ draw_text(Font font, s8 text, v2 pos, f32 rotation, Color colour) off.x += font.recs[idx].width; } rlPopMatrix(); + v2 result = {.x = off.x, .y = font.baseSize}; + return result; +} + +static b32 +bmv_equal(BPModifiableValue *a, BPModifiableValue *b) +{ + b32 result = (uintptr_t)a->value == (uintptr_t)b->value; + return result; } static void @@ -188,49 +197,135 @@ do_text_input(BeamformerCtx *ctx, i32 max_disp_chars, Rect r, Color colour) } } -struct listing { - s8 prefix; - s8 suffix; - f32 *data; - v2 limits; - f32 data_scale; - b32 editable; -}; - static void -parse_and_store_text_input(BeamformerCtx *ctx, struct listing *l) +parse_and_store_text_input(BeamformerCtx *ctx, BPModifiableValue *bmv) { f32 new_val = strtof(ctx->is.buf, NULL); /* TODO: allow zero for certain listings only */ - if (new_val / l->data_scale != *l->data) { - *l->data = new_val / l->data_scale; - CLAMP(*l->data, l->limits.x, l->limits.y); - ctx->flags |= DO_COMPUTE; - ctx->params->upload = 1; + if (new_val / bmv->scale != *bmv->value) { + *bmv->value = new_val / bmv->scale; + CLAMP(*bmv->value, bmv->limits.x, bmv->limits.y); + if (bmv->compute) { + ctx->flags |= DO_COMPUTE; + ctx->params->upload = 1; + } } } static void -set_text_input_idx(BeamformerCtx *ctx, i32 idx, struct listing *l, struct listing *last_l, Rect r, - v2 mouse) +set_text_input_idx(BeamformerCtx *ctx, BPModifiableValue bmv, Rect r, v2 mouse) { - if (ctx->is.idx != idx && ctx->is.idx != -1) - parse_and_store_text_input(ctx, last_l); - - ctx->is.buf_len = snprintf(ctx->is.buf, ARRAY_COUNT(ctx->is.buf), "%0.02f", - *l->data * l->data_scale); + if (ctx->is.store.value && !bmv_equal(&ctx->is.store, &bmv)) + parse_and_store_text_input(ctx, &ctx->is.store); - ctx->is.idx = idx; + ctx->is.store = bmv; ctx->is.cursor = -1; - if (ctx->is.idx == -1) + if (ctx->is.store.value == NULL) return; + ctx->is.buf_len = snprintf(ctx->is.buf, ARRAY_COUNT(ctx->is.buf), "%0.02f", + *bmv.value * bmv.scale); + ASSERT(CheckCollisionPointRec(mouse.rl, r.rl)); ctx->is.cursor_hover_p = (mouse.x - r.pos.x) / r.size.w; CLAMP01(ctx->is.cursor_hover_p); } +/* NOTE: This is kinda sucks no matter how you spin it. If we want the values to be + * left aligned in the center column we need to know the longest prefix length but + * without either hardcoding one of the prefixes as the longest one or measuring all + * of them we can't know this ahead of time. For now we hardcode this and manually + * adjust when needed */ +#define LISTING_LEFT_COLUMN_WIDTH 240.0f +#define LISTING_LINE_PAD 10.0f + +static Rect +do_value_listing(s8 prefix, s8 suffix, f32 value, Font font, Arena a, Rect r) +{ + v2 suffix_s = measure_text(font, suffix); + v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = r.pos.y}; + + s8 txt = s8alloc(&a, 64); + txt.len = snprintf((char *)txt.data, txt.len, "%0.02f", value); + v2 txt_p = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}; + + draw_text(font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); + draw_text(font, txt, txt_p, 0, colour_from_normalized(FG_COLOUR)); + draw_text(font, suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); + r.pos.y += suffix_s.h + LISTING_LINE_PAD; + r.size.y -= suffix_s.h + LISTING_LINE_PAD; + + return r; +} + +static Rect +do_text_input_listing(s8 prefix, s8 suffix, BPModifiableValue bmv, BeamformerCtx *ctx, Arena a, + Rect r, v2 mouse, f32 *hover_t) +{ + s8 buf = s8alloc(&a, 64); + s8 txt = buf; + v2 txt_s; + if (bmv_equal(&bmv, &ctx->is.store)) { + txt_s = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, + .data = (u8 *)ctx->is.buf}); + } else { + txt.len = snprintf((char *)txt.data, buf.len, "%0.02f", *bmv.value * bmv.scale); + txt_s = measure_text(ctx->font, txt); + } + + Rect edit_rect = { + .pos = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}, + .size = {.x = txt_s.w + TEXT_BOX_EXTRA_X, .y = txt_s.h} + }; + + b32 collides = CheckCollisionPointRec(mouse.rl, edit_rect.rl); + if (collides && !bmv_equal(&bmv, &ctx->is.store)) *hover_t += TEXT_HOVER_SPEED * ctx->dt; + else *hover_t -= TEXT_HOVER_SPEED * ctx->dt; + CLAMP01(*hover_t); + + if (collides) { + f32 mouse_scroll = GetMouseWheelMove(); + if (mouse_scroll) { + if (bmv_equal(&bmv, &ctx->is.store)) + set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); + *bmv.value += mouse_scroll / bmv.scale; + CLAMP(*bmv.value, bmv.limits.x, bmv.limits.y); + if (bmv.compute) { + ctx->flags |= DO_COMPUTE; + ctx->params->upload = 1; + } + txt.len = snprintf((char *)txt.data, buf.len, "%0.02f", *bmv.value * bmv.scale); + } + } + + if (!collides && bmv_equal(&bmv, &ctx->is.store) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); + txt.len = snprintf((char *)txt.data, buf.len, "%0.02f", *bmv.value * bmv.scale); + } + + if (collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + set_text_input_idx(ctx, bmv, edit_rect, mouse); + + Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); + + if (!bmv_equal(&bmv, &ctx->is.store)) { + draw_text(ctx->font, txt, edit_rect.pos, 0, colour); + } else { + do_text_input(ctx, 7, edit_rect, colour); + } + + v2 suffix_s = measure_text(ctx->font, suffix); + v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = r.pos.y}; + draw_text(ctx->font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); + draw_text(ctx->font, suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); + + r.pos.y += suffix_s.h + LISTING_LINE_PAD; + r.size.y -= suffix_s.h + LISTING_LINE_PAD; + + return r; +} + static void draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) { @@ -238,99 +333,57 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) f32 minx = bp->output_min_xz.x + 1e-6, maxx = bp->output_max_xz.x - 1e-6; f32 minz = bp->output_min_xz.y + 1e-6, maxz = bp->output_max_xz.y - 1e-6; - struct listing listings[] = { - { s8("Sampling Rate:"), s8("[MHz]"), &bp->sampling_frequency, {{0, 0}}, 1e-6, 0 }, - { s8("Center Frequency:"), s8("[MHz]"), &bp->center_frequency, {{0, 100e6}}, 1e-6, 1 }, - { s8("Speed of Sound:"), s8("[m/s]"), &bp->speed_of_sound, {{0, 1e6}}, 1, 1 }, - { s8("Min X Point:"), s8("[mm]"), &bp->output_min_xz.x, {{-1e3, maxx}}, 1e3, 1 }, - { s8("Max X Point:"), s8("[mm]"), &bp->output_max_xz.x, {{minx, 1e3}}, 1e3, 1 }, - { s8("Min Z Point:"), s8("[mm]"), &bp->output_min_xz.y, {{0, maxz}}, 1e3, 1 }, - { s8("Max Z Point:"), s8("[mm]"), &bp->output_max_xz.y, {{minz, 1e3}}, 1e3, 1 }, - { s8("Dynamic Range:"), s8("[dB]"), &ctx->fsctx.db, {{-120, 0}}, 1, 1 }, - { s8("Y Position:"), s8("[mm]"), &bp->off_axis_pos, {{minx*2, maxx*2}}, 1e3, 1 }, - }; - static f32 hover_t[ARRAY_COUNT(listings)]; + Rect draw_r = r; + draw_r.pos.y += 20; + draw_r.pos.x += 20; + draw_r.size.x -= 20; + draw_r.size.y -= 20; - f32 line_pad = 10; + draw_r = do_value_listing(s8("Sampling Frequency:"), s8("[MHz]"), + bp->sampling_frequency * 1e-6, ctx->font, arena, draw_r); - v2 pos = r.pos; - pos.y += 50; - pos.x += 20; + static f32 hover_t[8]; + i32 idx = 0; - s8 txt = s8alloc(&arena, 64); - f32 max_prefix_len = 0; - for (i32 i = 0; i < ARRAY_COUNT(listings); i++) { - struct listing *l = listings + i; - draw_text(ctx->font, l->prefix, pos, 0, colour_from_normalized(FG_COLOUR)); - v2 prefix_s = measure_text(ctx->font, l->prefix); - if (prefix_s.w > max_prefix_len) - max_prefix_len = prefix_s.w; - pos.y += prefix_s.y + line_pad; - } - pos.y = 50 + r.pos.y; - - for (i32 i = 0; i < ARRAY_COUNT(listings); i++) { - struct listing *l = listings + i; - - s8 tmp_s = txt; - v2 txt_s; - if (ctx->is.idx == i) { - txt_s = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, - .data = (u8 *)ctx->is.buf}); - } else { - tmp_s.len = snprintf((char *)txt.data, txt.len, "%0.02f", *l->data * l->data_scale); - txt_s = measure_text(ctx->font, tmp_s); - } + BPModifiableValue bmv; + bmv = (BPModifiableValue){&bp->center_frequency, (v2){.y = 100e6}, 1e-6, 1}; + draw_r = do_text_input_listing(s8("Center Frequency:"), s8("[MHz]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - Rect edit_rect = { - .pos = {.x = pos.x + max_prefix_len + 15, .y = pos.y}, - .size = {.x = txt_s.w + TEXT_BOX_EXTRA_X, .y = txt_s.h} - }; + bmv = (BPModifiableValue){&bp->speed_of_sound, (v2){.y = 1e6}, 1, 1}; + draw_r = do_text_input_listing(s8("Speed of Sound:"), s8("[m/s]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - b32 collides = CheckCollisionPointRec(mouse.rl, edit_rect.rl); - if (collides && l->editable) { - f32 mouse_scroll = GetMouseWheelMove(); - if (mouse_scroll) { - *l->data += mouse_scroll / l->data_scale; - CLAMP(*l->data, l->limits.x, l->limits.y); - ctx->flags |= DO_COMPUTE; - ctx->params->upload = 1; - } - } + bmv = (BPModifiableValue){&bp->output_min_xz.x, (v2){.x = -1e3, .y = maxx}, 1e3, 1}; + draw_r = do_text_input_listing(s8("Min X Point:"), s8("[mm]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - if (collides && ctx->is.idx != i && l->editable) - hover_t[i] += TEXT_HOVER_SPEED * ctx->dt; - else - hover_t[i] -= TEXT_HOVER_SPEED * ctx->dt; - CLAMP01(hover_t[i]); + bmv = (BPModifiableValue){&bp->output_max_xz.x, (v2){.x = minx, .y = 1e3}, 1e3, 1}; + draw_r = do_text_input_listing(s8("Max X Point:"), s8("[mm]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - if (!collides && ctx->is.idx == i && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - set_text_input_idx(ctx, -1, l, l, (Rect){0}, mouse); - tmp_s.len = snprintf((char *)txt.data, txt.len, "%0.02f", *l->data * l->data_scale); - } + bmv = (BPModifiableValue){&bp->output_min_xz.y, (v2){.y = maxz}, 1e3, 1}; + draw_r = do_text_input_listing(s8("Min Z Point:"), s8("[mm]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - if (collides && l->editable && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) - set_text_input_idx(ctx, i, l, listings + ctx->is.idx, edit_rect, mouse); + bmv = (BPModifiableValue){&bp->output_max_xz.y, (v2){.x = minz, .y = 1e3}, 1e3, 1}; + draw_r = do_text_input_listing(s8("Max Z Point:"), s8("[mm]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, hover_t[i])); + bmv = (BPModifiableValue){&bp->off_axis_pos, (v2){.x = minx * 2, .y = maxx * 2}, 1e3, 1}; + draw_r = do_text_input_listing(s8("Y Position:"), s8("[mm]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - if (ctx->is.idx != i) { - draw_text(ctx->font, tmp_s, edit_rect.pos, 0, colour); - } else { - do_text_input(ctx, 7, edit_rect, colour); - } + bmv = (BPModifiableValue){&ctx->fsctx.db, (v2){.x = -120}, 1, 0}; + draw_r = do_text_input_listing(s8("Dynamic Range:"), s8("[dB]"), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); - v2 suffix_s = measure_text(ctx->font, l->suffix); - v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = pos.y}; - draw_text(ctx->font, l->suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); - pos.y += txt_s.y + line_pad; - } + /* NOTE: if C compilers didn't suck this would be a static assert */ + ASSERT(idx <= ARRAY_COUNT(hover_t)); - if (IsKeyPressed(KEY_ENTER) && ctx->is.idx != -1) { - struct listing *l = listings + ctx->is.idx; - set_text_input_idx(ctx, -1, l, l, (Rect){0}, mouse); - } + if (IsKeyPressed(KEY_ENTER) && ctx->is.store.value) + set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); } @@ -338,8 +391,6 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) static void draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) { - DrawFPS(20, 20); - uv2 ws = ctx->window_size; static s8 labels[CS_LAST] = {