ogl_beamforming

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

Commit: 7cb611b27dc68f0645564952188a8d606e56260e
Parent: a6f14ec436cd239070199259fc75d0913d409e8d
Author: Randy Palamar
Date:   Sat, 17 Aug 2024 21:44:13 -0600

remove absolutely horrible raylib functions

MeasureTextEx, DrawTextEx, and GetGlyphIndex all waste tons of CPU
cycles for no reason. When implemented properly they are very simple.

Diffstat:
Mbeamformer.c | 204+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mutil.h | 2++
2 files changed, 128 insertions(+), 78 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -215,16 +215,71 @@ lerp_v4(v4 a, v4 b, f32 t) }; } +static v2 +measure_text(Font font, s8 text) +{ + v2 result = {.y = font.baseSize}; + for (size i = 0; i < text.len; i++) { + /* NOTE: assumes font glyphs are ordered ASCII */ + i32 idx = (i32)text.data[i] - 0x20; + result.x += font.glyphs[idx].advanceX; + if (font.glyphs[idx].advanceX == 0) + result.x += (font.recs[idx].width + font.glyphs[idx].offsetX); + } + return result; +} + +static void +draw_text(Font font, s8 text, v2 pos, f32 rotation, Color colour) +{ + rlPushMatrix(); + + rlTranslatef(pos.x, pos.y, 0); + rlRotatef(rotation, 0, 0, 1); + + v2 off = {0}; + for (size i = 0; i < text.len; i++) { + /* NOTE: assumes font glyphs are ordered ASCII */ + i32 idx = text.data[i] - 0x20; + Rectangle dst = { + off.x + font.glyphs[idx].offsetX - font.glyphPadding, + off.y + font.glyphs[idx].offsetY - font.glyphPadding, + font.recs[idx].width + 2.0f * font.glyphPadding, + font.recs[idx].height + 2.0f * font.glyphPadding + }; + Rectangle src = { + font.recs[idx].x - font.glyphPadding, + font.recs[idx].y - font.glyphPadding, + font.recs[idx].width + 2.0f * font.glyphPadding, + font.recs[idx].height + 2.0f * font.glyphPadding + }; + DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); + + off.x += font.glyphs[idx].advanceX; + if (font.glyphs[idx].advanceX == 0) + off.x += font.recs[idx].width; + } + rlPopMatrix(); +} + static void -do_text_input(BeamformerCtx *ctx, i32 max_chars, Rect r, Color colour) +do_text_input(BeamformerCtx *ctx, i32 max_disp_chars, Rect r, Color colour) { - v2 ts = {.rl = MeasureTextEx(ctx->font, ctx->is.buf, ctx->font_size, 0)}; + v2 ts = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, .data = (u8 *)ctx->is.buf}); v2 pos = {.x = r.pos.x, .y = r.pos.y + (r.size.y - ts.y) / 2}; - i32 buf_delta = ctx->is.buf_len - max_chars; + i32 buf_delta = ctx->is.buf_len - max_disp_chars; if (buf_delta < 0) buf_delta = 0; - char *buf = ctx->is.buf + buf_delta; - DrawTextEx(ctx->font, buf, pos.rl, ctx->font_size, 0, colour); + s8 buf = {.len = ctx->is.buf_len - buf_delta, .data = (u8 *)ctx->is.buf + buf_delta}; + { + /* NOTE: drop a char if the subtext still doesn't fit */ + v2 nts = measure_text(ctx->font, buf); + if (nts.w > 0.96 * r.size.w) { + buf.data++; + buf.len--; + } + } + draw_text(ctx->font, buf, pos, 0, colour); ctx->is.cursor_blink_t = move_towards_f32(ctx->is.cursor_blink_t, ctx->is.cursor_blink_target, 1.5 * ctx->dt); @@ -238,36 +293,36 @@ do_text_input(BeamformerCtx *ctx, i32 max_chars, Rect r, Color colour) Color cursor_colour = colour_from_normalized(lerp_v4(bg, FOCUSED_COLOUR, ctx->is.cursor_blink_t)); + /* NOTE: guess a cursor position */ if (ctx->is.cursor == -1) { - f32 x_off = TEXT_BOX_EXTRA_X, x_bounds = r.size.w * ctx->is.cursor_hover_p; - u32 i; + /* NOTE: extra offset to help with putting a cursor at idx 0 */ + #define TEXT_HALF_CHAR_WIDTH 10 + f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * ctx->is.cursor_hover_p; + i32 i; for (i = 0; i < ctx->is.buf_len && x_off < x_bounds; i++) { - u32 idx = GetGlyphIndex(ctx->font, ctx->is.buf[i]); + /* NOTE: assumes font glyphs are ordered ASCII */ + i32 idx = ctx->is.buf[i] - 0x20; + x_off += ctx->font.glyphs[idx].advanceX; if (ctx->font.glyphs[idx].advanceX == 0) x_off += ctx->font.recs[idx].width; - else - x_off += ctx->font.glyphs[idx].advanceX; } ctx->is.cursor = i; } - /* NOTE: Braindead NULL termination stupidity */ - char saved_c = buf[ctx->is.cursor - buf_delta]; - buf[ctx->is.cursor - buf_delta] = 0; - - v2 sts = {.rl = MeasureTextEx(ctx->font, buf, ctx->font_size, 0)}; - f32 cursor_x = r.pos.x + sts.x; - f32 cursor_width = ctx->is.cursor == ctx->is.buf_len ? 20 : 6; - - buf[ctx->is.cursor - buf_delta] = saved_c; + buf.len = ctx->is.cursor - buf_delta; + v2 sts = measure_text(ctx->font, buf); + f32 cursor_x = r.pos.x + sts.x; + f32 cursor_width; + if (ctx->is.cursor == ctx->is.buf_len) cursor_width = MIN(ctx->window_size.w * 0.03, 20); + else cursor_width = MIN(ctx->window_size.w * 0.01, 6); Rect cursor_r = { .pos = {.x = cursor_x, .y = pos.y}, .size = {.w = cursor_width, .h = ts.h}, }; - DrawRectangleRec(cursor_r.rl, cursor_colour); + DrawRectanglePro(cursor_r.rl, (Vector2){0}, 0, cursor_colour); /* NOTE: handle multiple input keys on a single frame */ i32 key = GetCharPressed(); @@ -303,20 +358,20 @@ do_text_input(BeamformerCtx *ctx, i32 max_chars, Rect r, Color colour) mem_move(ctx->is.buf + ctx->is.cursor + 1, ctx->is.buf + ctx->is.cursor, ctx->is.buf_len - ctx->is.cursor); - ctx->is.buf[--ctx->is.buf_len] = 0; + ctx->is.buf_len--; } if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && ctx->is.cursor < ctx->is.buf_len) { mem_move(ctx->is.buf + ctx->is.cursor + 1, ctx->is.buf + ctx->is.cursor, ctx->is.buf_len - ctx->is.cursor); - ctx->is.buf[--ctx->is.buf_len] = 0; + ctx->is.buf_len--; } } struct listing { - char *prefix; - char *suffix; + s8 prefix; + s8 suffix; f32 *data; v2 limits; f32 data_scale; @@ -365,15 +420,15 @@ 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[] = { - { "Sampling Rate:", "[MHz]", &bp->sampling_frequency, {{0, 0}}, 1e-6, 0 }, - { "Center Frequency:", "[MHz]", &bp->center_frequency, {{0, 100e6}}, 1e-6, 1 }, - { "Speed of Sound:", "[m/s]", &bp->speed_of_sound, {{0, 1e6}}, 1, 1 }, - { "Min X Point:", "[mm]", &bp->output_min_xz.x, {{-1e3, maxx}}, 1e3, 1 }, - { "Max X Point:", "[mm]", &bp->output_max_xz.x, {{minx, 1e3}}, 1e3, 1 }, - { "Min Z Point:", "[mm]", &bp->output_min_xz.y, {{0, maxz}}, 1e3, 1 }, - { "Max Z Point:", "[mm]", &bp->output_max_xz.y, {{minz, 1e3}}, 1e3, 1 }, - { "Dynamic Range:", "[dB]", &ctx->fsctx.db, {{-120, 0}}, 1, 1 }, - { "Y Position:", "[mm]", &bp->off_axis_pos, {{minx*2, maxx*2}}, 1e3, 1 }, + { 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)]; @@ -388,10 +443,8 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) f32 max_prefix_len = 0; for (i32 i = 0; i < ARRAY_COUNT(listings); i++) { struct listing *l = listings + i; - DrawTextEx(ctx->font, l->prefix, pos.rl, ctx->font_size, ctx->font_spacing, - colour_from_normalized(FG_COLOUR)); - v2 prefix_s = {.rl = MeasureTextEx(ctx->font, l->prefix, ctx->font_size, - ctx->font_spacing)}; + 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; @@ -401,14 +454,14 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) 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.rl = MeasureTextEx(ctx->font, ctx->is.buf, ctx->font_size, - ctx->font_spacing); + txt_s = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, + .data = (u8 *)ctx->is.buf}); } else { - snprintf((char *)txt.data, txt.len, "%0.02f", *l->data * l->data_scale); - txt_s.rl = MeasureTextEx(ctx->font, (char *)txt.data, ctx->font_size, - ctx->font_spacing); + tmp_s.len = snprintf((char *)txt.data, txt.len, "%0.02f", *l->data * l->data_scale); + txt_s = measure_text(ctx->font, tmp_s); } Rect edit_rect = { @@ -435,7 +488,7 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) if (!collides && ctx->is.idx == i && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { set_text_input_idx(ctx, -1, l, l, (Rect){0}, mouse); - snprintf((char *)txt.data, txt.len, "%0.02f", *l->data * l->data_scale); + tmp_s.len = snprintf((char *)txt.data, txt.len, "%0.02f", *l->data * l->data_scale); } if (collides && l->editable && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) @@ -444,17 +497,14 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, hover_t[i])); if (ctx->is.idx != i) { - DrawTextEx(ctx->font, (char *)txt.data, edit_rect.pos.rl, ctx->font_size, - ctx->font_spacing, colour); + draw_text(ctx->font, tmp_s, edit_rect.pos, 0, colour); } else { do_text_input(ctx, 7, edit_rect, colour); } - v2 suffix_s = {.rl = MeasureTextEx(ctx->font, l->suffix, ctx->font_size, - ctx->font_spacing)}; + 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}; - DrawTextEx(ctx->font, l->suffix, suffix_p.rl, ctx->font_size, ctx->font_spacing, - colour_from_normalized(FG_COLOUR)); + draw_text(ctx->font, l->suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); pos.y += txt_s.y + line_pad; } @@ -470,15 +520,14 @@ draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) DrawFPS(20, 20); uv2 ws = ctx->window_size; - u32 fontsize = ctx->font_size; - u32 fontspace = ctx->font_spacing; - - static char *labels[CS_LAST] = { - [CS_CUDA_DECODE_AND_DEMOD] = "CUDA Decoding:", - [CS_HADAMARD] = "Decoding:", - [CS_LPF] = "LPF:", - [CS_MIN_MAX] = "Min/Max:", - [CS_UFORCES] = "UFORCES:", + + static s8 labels[CS_LAST] = { + [CS_CUDA_DECODE_AND_DEMOD] = s8("CUDA Decoding:"), + [CS_HADAMARD] = s8("Decoding:"), + [CS_HERCULES] = s8("HERCULES:"), + [CS_LPF] = s8("LPF:"), + [CS_MIN_MAX] = s8("Min/Max:"), + [CS_UFORCES] = s8("UFORCES:"), }; ComputeShaderCtx *cs = &ctx->csctx; @@ -488,29 +537,28 @@ draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) u32 stages = ctx->params->compute_stages_count; for (u32 i = 0; i < stages; i++) { - u32 index = ctx->params->compute_stages[i]; - v2 txt_fs = {.rl = MeasureTextEx(ctx->font, labels[index], fontsize, fontspace)}; + u32 index = ctx->params->compute_stages[i]; + v2 txt_fs = measure_text(ctx->font, labels[index]); pos.y -= txt_fs.y; + draw_text(ctx->font, labels[index], pos, 0, colour_from_normalized(FG_COLOUR)); - DrawTextEx(ctx->font, labels[index], pos.rl, fontsize, fontspace, - colour_from_normalized(FG_COLOUR)); - - snprintf((char *)txt_buf.data, txt_buf.len, "%0.02e [s]", cs->last_frame_time[index]); - txt_fs.rl = MeasureTextEx(ctx->font, (char *)txt_buf.data, fontsize, fontspace); + s8 tmp = txt_buf; + tmp.len = snprintf((char *)txt_buf.data, txt_buf.len, "%0.02e [s]", + cs->last_frame_time[index]); + txt_fs = measure_text(ctx->font, tmp); v2 rpos = {.x = r.pos.x + r.size.w - txt_fs.w, .y = pos.y}; - DrawTextEx(ctx->font, (char *)txt_buf.data, rpos.rl, fontsize, fontspace, - colour_from_normalized(FG_COLOUR)); + draw_text(ctx->font, tmp, rpos, 0, colour_from_normalized(FG_COLOUR)); } { static v2 pos = {.x = 32, .y = 128}; static v2 scale = {.x = 1.0, .y = 1.0}; static u32 txt_idx = 0; - static char *txt[2] = { "-_-", "^_^" }; + static s8 txt[2] = { s8("-_-"), s8("^_^") }; static v2 ts[2]; if (ts[0].x == 0) { - ts[0] = (v2){ .rl = MeasureTextEx(ctx->font, txt[0], fontsize, fontspace) }; - ts[1] = (v2){ .rl = MeasureTextEx(ctx->font, txt[1], fontsize, fontspace) }; + ts[0] = measure_text(ctx->font, txt[0]); + ts[1] = measure_text(ctx->font, txt[1]); } pos.x += 130 * ctx->dt * scale.x; @@ -528,7 +576,7 @@ draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) scale.y *= -1.0; } - DrawTextEx(ctx->font, txt[txt_idx], pos.rl, fontsize, fontspace, RED); + draw_text(ctx->font, txt[txt_idx], pos, 0, RED); } } @@ -661,10 +709,10 @@ do_beamformer(BeamformerCtx *ctx, Arena arena) lr.size.w = 420; if (output_dim.x > 1e-6 && output_dim.y > 1e-6) { - s8 txt = s8alloc(&arena, 64); - snprintf((char *)txt.data, txt.len, "%+0.01f mm", -88.8f); - v2 txt_s = {.rl = MeasureTextEx(ctx->font, (char *)txt.data, - ctx->font_size, ctx->font_spacing)}; + s8 txt = s8alloc(&arena, 64); + s8 tmp = txt; + tmp.len = snprintf((char *)txt.data, txt.len, "%+0.01f mm", -88.8f); + v2 txt_s = measure_text(ctx->font, tmp); rr.size.w = wr.size.w - lr.size.w; rr.pos.x = lr.pos.x + lr.size.w; @@ -746,9 +794,9 @@ do_beamformer(BeamformerCtx *ctx, Arena arena) f32 rot[2] = {90, 0}; for (u32 j = 0; j <= line_count; j++) { DrawLineEx(start_pos.rl, end_pos.rl, 3, colour_from_normalized(FG_COLOUR)); - snprintf((char *)txt.data, txt.len, fmt[i], mm); - DrawTextPro(ctx->font, (char *)txt.data, txt_pos.rl, (Vector2){0}, - rot[i], ctx->font_size, ctx->font_spacing, txt_colour); + s8 tmp = txt; + tmp.len = snprintf((char *)txt.data, txt.len, fmt[i], mm); + draw_text(ctx->font, tmp, txt_pos, rot[i], txt_colour); start_pos.E[i] += inc; end_pos.E[i] += inc; txt_pos.E[i] += inc; diff --git a/util.h b/util.h @@ -20,6 +20,7 @@ #define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) #define CLAMP01(x) CLAMP(x, 0, 1) #define ISPOWEROF2(a) (((a) & ((a) - 1)) == 0) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define ORONE(x) ((x)? (x) : 1) @@ -41,6 +42,7 @@ typedef ptrdiff_t size; typedef struct { u8 *beg, *end; } Arena; typedef struct { size len; u8 *data; } s8; +#define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} typedef union { struct { u32 x, y; };