ogl_beamforming

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

Commit: f22798d2a027f8a51d0187e0da7cd9451eed0cb9
Parent: d93ee2a1092dceaf552f960c356c145e36b2f8c2
Author: Randy Palamar
Date:   Sat,  7 Sep 2024 09:39:34 -0600

add button for dumping a raw volume to file

This is not hooked up yet but will be soon.

Diffstat:
Mbeamformer.h | 34++++++++++++++++++++++++++++++----
Mmain.c | 5++++-
Mui.c | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mutil.h | 6++++++
4 files changed, 230 insertions(+), 45 deletions(-)

diff --git a/beamformer.h b/beamformer.h @@ -17,11 +17,17 @@ #define FOCUSED_COLOUR (v4){.r = 0.86, .g = 0.28, .b = 0.21, .a = 1.0} #define HOVERED_COLOUR (v4){.r = 0.11, .g = 0.50, .b = 0.59, .a = 1.0} +#define INFO_COLUMN_WIDTH 560 /* NOTE: extra space used for allowing mouse clicks after end of text */ #define TEXT_BOX_EXTRA_X 10.0f #define TEXT_HOVER_SPEED 5.0f +#define RECT_BTN_COLOUR (Color){.r = 0x43, .g = 0x36, .b = 0x3a, .a = 0xff} +#define RECT_BTN_BORDER_COLOUR (Color){.r = 0x00, .g = 0x00, .b = 0x00, .a = 0xCC} +#define RECT_BTN_ROUNDNESS 0.3f +#define RECT_BTN_BORDER_WIDTH 6.0f + typedef union { struct { f32 x, y; }; struct { f32 w, h; }; @@ -55,11 +61,17 @@ enum gl_vendor_ids { GL_VENDOR_NVIDIA, }; +enum modifiable_value_flags { + MV_CAUSES_COMPUTE = 1 << 0, + MV_FLOAT = 1 << 1, + MV_INT = 1 << 2, + MV_POWER_OF_TWO = 1 << 30, +}; typedef struct { - f32 *value; - v2 limits; - f32 scale; - b32 compute; + void *value; + u32 flags; + f32 scale; + union {v2 flimits; iv2 ilimits;}; } BPModifiableValue; typedef struct { @@ -179,6 +191,19 @@ typedef struct { f32 db; } FragmentShaderCtx; +enum export_state { + ES_START = (1 << 0), + ES_COMPUTING = (1 << 1), + ES_DONE = (1 << 2), +}; + +typedef struct { + uv4 volume_dim; + u32 rf_data_ssbo; + u32 volume_texture; + u32 state; +} ExportCtx; + typedef struct { uv2 window_size; u32 flags; @@ -200,6 +225,7 @@ typedef struct { ComputeShaderCtx csctx; FragmentShaderCtx fsctx; + ExportCtx export_ctx; os_pipe data_pipe; u32 partial_transfer_count; diff --git a/main.c b/main.c @@ -167,13 +167,16 @@ main(void) Arena temp_memory = os_alloc_arena((Arena){0}, 8 * MEGABYTE); - ctx.window_size = (uv2){.w = 960, .h = 720}; + ctx.window_size = (uv2){.w = 1280, .h = 840}; ctx.out_data_dim = (uv4){.x = 256, .y = 1024, .z = 1}; + ctx.export_ctx.volume_dim = (uv4){.x = 1, .y = 1, .z = 1}; + SetConfigFlags(FLAG_VSYNC_HINT); InitWindow(ctx.window_size.w, ctx.window_size.h, "OGL Beamformer"); /* NOTE: do this after initing so that the window starts out floating in tiling wm */ SetWindowState(FLAG_WINDOW_RESIZABLE); + SetWindowMinSize(INFO_COLUMN_WIDTH * 2, ctx.window_size.h); ctx.font_size = 32; ctx.font_spacing = 0; diff --git a/ui.c b/ui.c @@ -8,6 +8,13 @@ colour_from_normalized(v4 rgba) .b = rgba.b * 255.0f, .a = rgba.a * 255.0f}; } +static Color +fade(Color a, f32 alpha) +{ + a.a = (u8)((f32)a.a * alpha); + return a; +} + static f32 move_towards_f32(f32 current, f32 target, f32 delta) { @@ -23,6 +30,12 @@ move_towards_f32(f32 current, f32 target, f32 delta) return current; } +static f32 +lerp(f32 a, f32 b, f32 t) +{ + return a + t * (b - a); +} + static v4 lerp_v4(v4 a, v4 b, f32 t) { @@ -83,6 +96,28 @@ draw_text(Font font, s8 text, v2 pos, f32 rotation, Color colour) return result; } +static Rect +scale_rect_centered(Rect r, v2 scale) +{ + Rect or = r; + r.size.w *= scale.x; + r.size.h *= scale.y; + r.pos.x += (or.size.w - r.size.w) / 2; + r.pos.y += (or.size.h - r.size.h) / 2; + return r; +} + +static v2 +center_align_text_in_rect(Rect r, s8 text, Font font) +{ + v2 ts = measure_text(font, text); + v2 delta = { .w = r.size.w - ts.w, .h = r.size.h - ts.h }; + return (v2) { + .x = r.pos.x + 0.5 * delta.w, + .y = r.pos.y + 0.5 * delta.h, + }; +} + static b32 bmv_equal(BPModifiableValue *a, BPModifiableValue *b) { @@ -90,6 +125,61 @@ bmv_equal(BPModifiableValue *a, BPModifiableValue *b) return result; } +static f32 +bmv_scaled_value(BPModifiableValue *a) +{ + f32 result; + if (a->flags & MV_FLOAT) result = *(f32 *)a->value * a->scale; + else result = *(i32 *)a->value * a->scale; + return result; +} + +static b32 +bmv_store_value(BPModifiableValue *bmv, f32 new_val, b32 from_scroll) +{ + if (bmv->flags & MV_FLOAT) { + f32 *value = bmv->value; + if (new_val / bmv->scale == *value) + return 0; + *value = new_val / bmv->scale; + CLAMP(*value, bmv->flimits.x, bmv->flimits.y); + } else if (bmv->flags & MV_INT && bmv->flags & MV_POWER_OF_TWO) { + i32 *value = bmv->value; + if (new_val == *value) + return 0; + if (from_scroll && new_val > *value) *value <<= 1; + else *value = round_down_power_of_2(new_val); + CLAMP(*value, bmv->ilimits.x, bmv->ilimits.y); + } else { + ASSERT(bmv->flags & MV_INT); + i32 *value = bmv->value; + if (new_val / bmv->scale == *value) + return 0; + *value = new_val / bmv->scale; + CLAMP(*value, bmv->ilimits.x, bmv->ilimits.y); + } + return 1; +} + +static s8 +bmv_sprint(BPModifiableValue *bmv, s8 buf) +{ + s8 result = buf; + if (bmv->flags & MV_FLOAT) { + f32 *value = bmv->value; + size len = snprintf((char *)buf.data, buf.len, "%0.02f", *value * bmv->scale); + ASSERT(len <= buf.len); + result.len = len; + } else { + ASSERT(bmv->flags & MV_INT); + i32 *value = bmv->value; + size len = snprintf((char *)buf.data, buf.len, "%d", (i32)(*value * bmv->scale)); + ASSERT(len <= buf.len); + result.len = len; + } + return result; +} + static void do_text_input(BeamformerCtx *ctx, i32 max_disp_chars, Rect r, Color colour) { @@ -201,14 +291,10 @@ static void 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 / 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; - } + b32 changed = bmv_store_value(bmv, new_val, 0); + if (changed && bmv->flags & MV_CAUSES_COMPUTE) { + ctx->flags |= DO_COMPUTE; + ctx->params->upload = 1; } } @@ -224,8 +310,8 @@ set_text_input_idx(BeamformerCtx *ctx, BPModifiableValue bmv, Rect r, v2 mouse) 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); + s8 ibuf = bmv_sprint(&bmv, (s8){.data = (u8 *)ctx->is.buf, .len = ARRAY_COUNT(ctx->is.buf)}); + ctx->is.buf_len = ibuf.len; ASSERT(CheckCollisionPointRec(mouse.rl, r.rl)); ctx->is.cursor_hover_p = (mouse.x - r.pos.x) / r.size.w; @@ -237,7 +323,7 @@ set_text_input_idx(BeamformerCtx *ctx, BPModifiableValue bmv, Rect r, v2 mouse) * 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_LEFT_COLUMN_WIDTH 270.0f #define LISTING_LINE_PAD 10.0f static Rect @@ -266,12 +352,14 @@ do_text_input_listing(s8 prefix, s8 suffix, BPModifiableValue bmv, BeamformerCtx s8 buf = s8alloc(&a, 64); s8 txt = buf; v2 txt_s; - if (bmv_equal(&bmv, &ctx->is.store)) { + + b32 bmv_active = bmv_equal(&bmv, &ctx->is.store); + if (bmv_active) { 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); + txt = bmv_sprint(&bmv, buf); + txt_s = measure_text(ctx->font, txt); } Rect edit_rect = { @@ -280,28 +368,28 @@ do_text_input_listing(s8 prefix, s8 suffix, BPModifiableValue bmv, BeamformerCtx }; 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; + if (collides && !bmv_active) *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)) + if (bmv_active) 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) { + f32 old_val = bmv_scaled_value(&bmv); + b32 changed = bmv_store_value(&bmv, old_val + mouse_scroll, 1); + if (changed && bmv.flags & MV_CAUSES_COMPUTE) { ctx->flags |= DO_COMPUTE; ctx->params->upload = 1; } - txt.len = snprintf((char *)txt.data, buf.len, "%0.02f", *bmv.value * bmv.scale); + txt = bmv_sprint(&bmv, buf); } } 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); + txt = bmv_sprint(&bmv, buf); } if (collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) @@ -326,9 +414,44 @@ do_text_input_listing(s8 prefix, s8 suffix, BPModifiableValue bmv, BeamformerCtx return r; } +static b32 +do_text_button(BeamformerCtx *ctx, s8 text, Rect r, v2 mouse, f32 *hover_t) +{ + b32 hovered = CheckCollisionPointRec(mouse.rl, r.rl); + b32 pressed = 0; + pressed |= (hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); + pressed |= (hovered && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)); + + if (hovered) *hover_t += TEXT_HOVER_SPEED * ctx->dt; + else *hover_t -= TEXT_HOVER_SPEED * ctx->dt; + CLAMP01(*hover_t); + + f32 param = lerp(1, 1.04, *hover_t); + v2 bscale = (v2){ + .x = param + RECT_BTN_BORDER_WIDTH / r.size.w, + .y = param + RECT_BTN_BORDER_WIDTH / r.size.h, + }; + Rect sr = scale_rect_centered(r, (v2){.x = param, .y = param}); + Rect sb = scale_rect_centered(r, bscale); + DrawRectangleRounded(sb.rl, RECT_BTN_ROUNDNESS, 0, RECT_BTN_BORDER_COLOUR); + DrawRectangleRounded(sr.rl, RECT_BTN_ROUNDNESS, 0, RECT_BTN_COLOUR); + + v2 tpos = center_align_text_in_rect(r, text, ctx->font); + v2 spos = {.x = tpos.x + 1.75, .y = tpos.y + 2}; + v4 colour = lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t); + + draw_text(ctx->font, text, spos, 0, fade(BLACK, 0.8)); + draw_text(ctx->font, text, tpos, 0, colour_from_normalized(colour)); + + return pressed; +} + static void draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) { + if (IsKeyPressed(KEY_ENTER) && ctx->is.store.value) + set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); + BeamformerParameters *bp = &ctx->params->raw; f32 minx = bp->output_min_coordinate.x + 1e-6, maxx = bp->output_max_coordinate.x - 1e-6; @@ -343,46 +466,73 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) draw_r = do_value_listing(s8("Sampling Frequency:"), s8("[MHz]"), bp->sampling_frequency * 1e-6, ctx->font, arena, draw_r); - static f32 hover_t[8]; + static f32 hover_t[11]; i32 idx = 0; BPModifiableValue bmv; - bmv = (BPModifiableValue){&bp->center_frequency, (v2){.y = 100e6}, 1e-6, 1}; + bmv = (BPModifiableValue){&bp->center_frequency, MV_FLOAT|MV_CAUSES_COMPUTE, 1e-6, + .flimits = (v2){.y = 100e6}}; draw_r = do_text_input_listing(s8("Center Frequency:"), s8("[MHz]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->speed_of_sound, (v2){.y = 1e6}, 1, 1}; + bmv = (BPModifiableValue){&bp->speed_of_sound, MV_FLOAT|MV_CAUSES_COMPUTE, 1, + .flimits = (v2){.y = 1e6}}; draw_r = do_text_input_listing(s8("Speed of Sound:"), s8("[m/s]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_min_coordinate.x, (v2){.x = -1e3, .y = maxx}, 1e3, 1}; - draw_r = do_text_input_listing(s8("Min X Point:"), s8("[mm]"), bmv, ctx, arena, + bmv = (BPModifiableValue){&bp->output_min_coordinate.x, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, + .flimits = (v2){.x = -1e3, .y = maxx}}; + draw_r = do_text_input_listing(s8("Min Lateral Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_max_coordinate.x, (v2){.x = minx, .y = 1e3}, 1e3, 1}; - draw_r = do_text_input_listing(s8("Max X Point:"), s8("[mm]"), bmv, ctx, arena, + bmv = (BPModifiableValue){&bp->output_max_coordinate.x, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, + .flimits = (v2){.x = minx, .y = 1e3}}; + draw_r = do_text_input_listing(s8("Max Lateral Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_min_coordinate.z, (v2){.y = maxz}, 1e3, 1}; - draw_r = do_text_input_listing(s8("Min Z Point:"), s8("[mm]"), bmv, ctx, arena, + bmv = (BPModifiableValue){&bp->output_min_coordinate.z, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, + .flimits = (v2){.y = maxz}}; + draw_r = do_text_input_listing(s8("Min Axial Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_max_coordinate.z, (v2){.x = minz, .y = 1e3}, 1e3, 1}; - draw_r = do_text_input_listing(s8("Max Z Point:"), s8("[mm]"), bmv, ctx, arena, + bmv = (BPModifiableValue){&bp->output_max_coordinate.z, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, + .flimits = (v2){.x = minz, .y = 1e3}}; + draw_r = do_text_input_listing(s8("Max Axial Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&ctx->fsctx.db, (v2){.x = -120}, 1, 0}; + bmv = (BPModifiableValue){&ctx->fsctx.db, MV_FLOAT, 1, .flimits = (v2){.x = -120}}; draw_r = do_text_input_listing(s8("Dynamic Range:"), s8("[dB]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - /* NOTE: if C compilers didn't suck this would be a static assert */ - ASSERT(idx <= ARRAY_COUNT(hover_t)); + draw_r.pos.y += 4 * LISTING_LINE_PAD; + draw_r.size.y -= 4 * LISTING_LINE_PAD; - if (IsKeyPressed(KEY_ENTER) && ctx->is.store.value) - set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); -} + bmv = (BPModifiableValue){&ctx->export_ctx.volume_dim.x, MV_INT|MV_POWER_OF_TWO, 1, + .ilimits = (iv2){.x = 1, .y = 2048}}; + draw_r = do_text_input_listing(s8("Export Dimension X:"), s8(""), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); + bmv = (BPModifiableValue){&ctx->export_ctx.volume_dim.y, MV_INT|MV_POWER_OF_TWO, 1, + .ilimits = (iv2){.x = 1, .y = 2048}}; + draw_r = do_text_input_listing(s8("Export Dimension Y:"), s8(""), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); + + bmv = (BPModifiableValue){&ctx->export_ctx.volume_dim.z, MV_INT|MV_POWER_OF_TWO, 1, + .ilimits = (iv2){.x = 1, .y = 2048}}; + draw_r = do_text_input_listing(s8("Export Dimension Z:"), s8(""), bmv, ctx, arena, + draw_r, mouse, hover_t + idx++); + + Rect btn_r = draw_r; + btn_r.size.h = ctx->font.baseSize * 1.3; + btn_r.size.w *= 0.6; + if (do_text_button(ctx, s8("Dump Raw Volume"), btn_r, mouse, hover_t + idx++)) { + if (!ctx->export_ctx.state) + ctx->export_ctx.state = ES_START; + } + /* NOTE: if C compilers didn't suck this would be a static assert */ + ASSERT(idx <= ARRAY_COUNT(hover_t)); +} static void draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) @@ -482,7 +632,7 @@ draw_ui(BeamformerCtx *ctx, Arena arena) v2 mouse = { .rl = GetMousePosition() }; Rect wr = {.size = {.w = (f32)ctx->window_size.w, .h = (f32)ctx->window_size.h}}; Rect lr = wr, rr = wr; - lr.size.w = 420; + lr.size.w = INFO_COLUMN_WIDTH; if (output_dim.x > 1e-6 && output_dim.y > 1e-6) { s8 txt = s8alloc(&arena, 64); diff --git a/util.h b/util.h @@ -47,6 +47,12 @@ typedef struct { size len; u8 *data; } s8; #define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} typedef union { + struct { i32 x, y; }; + struct { i32 w, h; }; + i32 E[2]; +} iv2; + +typedef union { struct { u32 x, y; }; struct { u32 w, h; }; u32 E[2];