ogl_beamforming

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

Commit: 47ae9c26a59520dc11837c2b0da98a06975a82af
Parent: c60092a6a362c5431fe7e5ae5d8b00294e82cee4
Author: Randy Palamar
Date:   Sat, 15 Mar 2025 22:07:05 -0600

ui: implement a region based layout system

Diffstat:
Mbeamformer.c | 169+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mbeamformer.h | 34++++++++++++++++++++++------------
Mintrinsics.c | 1+
Mstatic.c | 15+--------------
Mui.c | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mutil.c | 32++++++++++++++++++++++++++++++++
6 files changed, 345 insertions(+), 189 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -56,7 +56,8 @@ frame_next(BeamformFrameIterator *bfi) } static void -alloc_beamform_frame(GLParams *gp, BeamformFrame *out, uv3 out_dim, u32 frame_index, s8 name) +alloc_beamform_frame(GLParams *gp, BeamformFrame *out, ComputeShaderStats *out_stats, + uv3 out_dim, u32 frame_index, s8 name) { out->dim.x = CLAMP(round_down_power_of_2(ORONE(out_dim.x)), 1, gp->max_3d_texture_dim); out->dim.y = CLAMP(round_down_power_of_2(ORONE(out_dim.y)), 1, gp->max_3d_texture_dim); @@ -80,8 +81,8 @@ alloc_beamform_frame(GLParams *gp, BeamformFrame *out, uv3 out_dim, u32 frame_in glTextureStorage3D(out->texture, out->mips, GL_RG32F, out->dim.x, out->dim.y, out->dim.z); LABEL_GL_OBJECT(GL_TEXTURE, out->texture, stream_to_s8(&label)); - glDeleteQueries(ARRAY_COUNT(out->timer_ids), out->timer_ids); - glCreateQueries(GL_TIME_ELAPSED, ARRAY_COUNT(out->timer_ids), out->timer_ids); + glDeleteQueries(ARRAY_COUNT(out_stats->timer_ids), out_stats->timer_ids); + glCreateQueries(GL_TIME_ELAPSED, ARRAY_COUNT(out_stats->timer_ids), out_stats->timer_ids); } static void @@ -89,7 +90,8 @@ alloc_output_image(BeamformerCtx *ctx, uv3 output_dim) { uv3 try_dim = make_valid_test_dim(output_dim); if (!uv3_equal(try_dim, ctx->averaged_frame.dim)) { - alloc_beamform_frame(&ctx->gl, &ctx->averaged_frame, try_dim, 0, + alloc_beamform_frame(&ctx->gl, &ctx->averaged_frame, + &ctx->averaged_frame_compute_stats, try_dim, 0, s8("Beamformed_Averaged_Data")); uv3 odim = ctx->averaged_frame.dim; @@ -242,6 +244,22 @@ DEBUG_EXPORT BEAMFORM_WORK_QUEUE_PUSH_COMMIT_FN(beamform_work_queue_push_commit) atomic_add(&q->queue, 1); } +static b32 +fill_frame_compute_work(BeamformerCtx *ctx, BeamformWork *work) +{ + b32 result = 0; + if (work) { + result = 1; + u32 frame_index = atomic_inc(&ctx->next_render_frame_index, 1); + frame_index %= ARRAY_COUNT(ctx->beamform_frames); + work->type = BW_COMPUTE; + work->frame.store = ctx->beamform_frames + frame_index; + work->frame.stats = ctx->beamform_frame_compute_stats + frame_index; + work->frame.store->ready_to_present = 0; + } + return result; +} + static void export_frame(BeamformerCtx *ctx, iptr handle, BeamformFrame *frame) { @@ -524,10 +542,11 @@ push_compute_shader_header(Arena *a, ComputeShaderID shader) return result; } -static void +static b32 reload_compute_shader(BeamformerCtx *ctx, s8 path, ComputeShaderReloadContext *csr, Arena tmp) { ComputeShaderCtx *cs = &ctx->csctx; + b32 result = 0; /* NOTE: arena works as stack (since everything here is 1 byte aligned) */ s8 header = {.data = tmp.beg}; @@ -554,6 +573,7 @@ reload_compute_shader(BeamformerCtx *ctx, s8 path, ComputeShaderReloadContext *c glUseProgram(cs->programs[csr->shader]); glBindBufferBase(GL_UNIFORM_BUFFER, 0, cs->shared_ubo); LABEL_GL_OBJECT(GL_PROGRAM, cs->programs[csr->shader], csr->label); + result = 1; } } @@ -564,9 +584,9 @@ reload_compute_shader(BeamformerCtx *ctx, s8 path, ComputeShaderReloadContext *c stream_append_s8(&buf, path); stream_append_byte(&buf, '\n'); ctx->platform.write_file(ctx->platform.error_file_handle, stream_to_s8(&buf)); - /* TODO(rnp): return an error and don't let the work item calling this function - * call pop off the queue; store a retry count and only fail after multiple tries */ } + + return result; } DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute) @@ -579,20 +599,28 @@ DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute) BeamformerParameters *bp = &ctx->params->raw; while (work) { + b32 can_commit = 1; switch (work->type) { case BW_RELOAD_SHADER: { ComputeShaderReloadContext *csr = work->reload_shader_ctx; - reload_compute_shader(ctx, csr->path, csr, arena); + b32 success = reload_compute_shader(ctx, csr->path, csr, arena); if (csr->shader == CS_DECODE) { csr->shader = CS_DECODE_FLOAT; - reload_compute_shader(ctx, csr->path, csr, arena); + success &= reload_compute_shader(ctx, csr->path, csr, arena); csr->shader = CS_DECODE; } - /* TODO(rnp): remove this */ - #define X(idx, name) cs->name##_id = glGetUniformLocation(cs->programs[idx], "u_" #name); - CS_UNIFORMS - #undef X + if (success) { + if (ctx->csctx.raw_data_ssbo) { + can_commit = 0; + fill_frame_compute_work(ctx, work); + } + + /* TODO(rnp): remove this */ + #define X(idx, name) cs->name##_id = glGetUniformLocation(cs->programs[idx], "u_" #name); + CS_UNIFORMS + #undef X + } } break; case BW_LOAD_RF_DATA: { if (cs->rf_raw_size != ctx->params->raw_data_size || @@ -629,7 +657,7 @@ DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute) atomic_store(&cs->processing_compute, 1); start_renderdoc_capture(&ctx->platform, gl_context); - BeamformFrame *frame = work->frame; + BeamformerWorkFrame *frame = &work->frame; if (ctx->params->upload) { glNamedBufferSubData(cs->shared_ubo, 0, sizeof(ctx->params->raw), &ctx->params->raw); @@ -640,61 +668,64 @@ DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute) glProgramUniform1ui(cs->programs[CS_DAS], cs->cycle_t_id, cycle_t++); uv3 try_dim = ctx->params->raw.output_points.xyz; - if (!uv3_equal(try_dim, frame->dim)) { - size frame_index = frame - ctx->beamform_frames; - alloc_beamform_frame(&ctx->gl, frame, try_dim, frame_index, - s8("Beamformed_Data")); + if (!uv3_equal(try_dim, frame->store->dim)) { + size frame_index = frame->store - ctx->beamform_frames; + alloc_beamform_frame(&ctx->gl, frame->store, frame->stats, try_dim, + frame_index, s8("Beamformed_Data")); } - frame->in_flight = 1; - frame->min_coordinate = ctx->params->raw.output_min_coordinate; - frame->max_coordinate = ctx->params->raw.output_max_coordinate; - frame->das_shader_id = ctx->params->raw.das_shader_id; - frame->compound_count = ctx->params->raw.dec_data_dim.z; + frame->store->in_flight = 1; + frame->store->min_coordinate = ctx->params->raw.output_min_coordinate; + frame->store->max_coordinate = ctx->params->raw.output_max_coordinate; + frame->store->das_shader_id = ctx->params->raw.das_shader_id; + frame->store->compound_count = ctx->params->raw.dec_data_dim.z; b32 did_sum_shader = 0; u32 stage_count = ctx->params->compute_stages_count; ComputeShaderID *stages = ctx->params->compute_stages; for (u32 i = 0; i < stage_count; i++) { did_sum_shader |= stages[i] == CS_SUM; - frame->timer_active[stages[i]] = 1; - glBeginQuery(GL_TIME_ELAPSED, frame->timer_ids[stages[i]]); - do_compute_shader(ctx, arena, frame, stages[i]); + frame->stats->timer_active[stages[i]] = 1; + glBeginQuery(GL_TIME_ELAPSED, frame->stats->timer_ids[stages[i]]); + do_compute_shader(ctx, arena, frame->store, stages[i]); glEndQuery(GL_TIME_ELAPSED); } /* NOTE(rnp): block until work completes so that we can record timings */ glFinish(); cs->processing_progress = 1; - for (u32 i = 0; i < ARRAY_COUNT(frame->timer_ids); i++) { + for (u32 i = 0; i < ARRAY_COUNT(frame->stats->timer_ids); i++) { u64 ns = 0; - if (frame->timer_active[i]) { - glGetQueryObjectui64v(frame->timer_ids[i], GL_QUERY_RESULT, &ns); - frame->timer_active[i] = 0; + if (frame->stats->timer_active[i]) { + glGetQueryObjectui64v(frame->stats->timer_ids[i], + GL_QUERY_RESULT, &ns); + frame->stats->timer_active[i] = 0; } - frame->compute_times[i] = (f32)ns / 1e9; + frame->stats->times[i] = (f32)ns / 1e9; } if (did_sum_shader) { ctx->averaged_frame.ready_to_present = 1; /* TODO(rnp): not really sure what to do here */ - mem_copy(frame->compute_times, ctx->averaged_frame.compute_times, - sizeof(frame->compute_times)); + mem_copy(frame->stats->times, ctx->averaged_frame_compute_stats.times, + sizeof(frame->stats->times)); } - frame->ready_to_present = 1; - cs->processing_compute = 0; + frame->store->ready_to_present = 1; + cs->processing_compute = 0; end_renderdoc_capture(&ctx->platform, gl_context); } break; case BW_SAVE_FRAME: { - BeamformFrame *frame = work->output_frame_ctx.frame; + BeamformFrame *frame = work->output_frame_ctx.frame.store; ASSERT(frame->ready_to_present); export_frame(ctx, work->output_frame_ctx.file_handle, frame); } break; } - beamform_work_queue_pop_commit(q); - work = beamform_work_queue_pop(q); + if (can_commit) { + beamform_work_queue_pop_commit(q); + work = beamform_work_queue_pop(q); + } } } @@ -716,18 +747,12 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) if (ctx->start_compute && !input->pipe_data_available) { if (ctx->beamform_frames[ctx->display_frame_index].ready_to_present) { BeamformWork *work = beamform_work_queue_push(ctx->beamform_work_queue); - if (work) { - /* TODO(rnp): cleanup all the duplicates of this */ - work->type = BW_COMPUTE; - work->frame = ctx->beamform_frames + ctx->next_render_frame_index++; - work->frame->ready_to_present = 0; - if (ctx->next_render_frame_index >= ARRAY_COUNT(ctx->beamform_frames)) - ctx->next_render_frame_index = 0; + if (fill_frame_compute_work(ctx, work)) { beamform_work_queue_push_commit(ctx->beamform_work_queue); + ctx->platform.wake_thread(ctx->platform.compute_worker.sync_handle); + ctx->start_compute = 0; } } - ctx->platform.wake_thread(ctx->platform.compute_worker.sync_handle); - ctx->start_compute = 0; } BeamformerParameters *bp = &ctx->params->raw; @@ -742,29 +767,25 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) beamform_work_queue_push_commit(ctx->beamform_work_queue); BeamformWork *compute = beamform_work_queue_push(ctx->beamform_work_queue); - if (compute) { - compute->type = BW_COMPUTE; - compute->frame = ctx->beamform_frames + ctx->next_render_frame_index++; - compute->frame->ready_to_present = 0; - if (ctx->next_render_frame_index >= ARRAY_COUNT(ctx->beamform_frames)) - ctx->next_render_frame_index = 0; + if (fill_frame_compute_work(ctx, compute)) beamform_work_queue_push_commit(ctx->beamform_work_queue); - if (ctx->params->export_next_frame) { - BeamformWork *export = beamform_work_queue_push(ctx->beamform_work_queue); - if (export) { - /* TODO: we don't really want the beamformer opening/closing files */ - iptr f = ctx->platform.open_for_write(ctx->params->export_pipe_name); - export->type = BW_SAVE_FRAME; - export->output_frame_ctx.file_handle = f; - if (ctx->params->raw.output_points.w > 1) - export->output_frame_ctx.frame = &ctx->averaged_frame; - else - export->output_frame_ctx.frame = compute->frame; - beamform_work_queue_push_commit(ctx->beamform_work_queue); + if (compute && ctx->params->export_next_frame) { + BeamformWork *export = beamform_work_queue_push(ctx->beamform_work_queue); + if (export) { + /* TODO: we don't really want the beamformer opening/closing files */ + iptr f = ctx->platform.open_for_write(ctx->params->export_pipe_name); + export->type = BW_SAVE_FRAME; + export->output_frame_ctx.file_handle = f; + if (ctx->params->raw.output_points.w > 1) { + export->output_frame_ctx.frame.store = &ctx->averaged_frame; + export->output_frame_ctx.frame.stats = &ctx->averaged_frame_compute_stats; + } else { + export->output_frame_ctx.frame = compute->frame; } - ctx->params->export_next_frame = 0; + beamform_work_queue_push_commit(ctx->beamform_work_queue); } + ctx->params->export_next_frame = 0; } if (ctx->params->upload) { @@ -792,7 +813,9 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) } /* NOTE: draw output image texture using render fragment shader */ - BeamformFrame *frame_to_draw = 0; + /* TODO(rnp): each beamform frame should have its own rendered view */ + BeamformFrame *frame_to_draw = 0; + ComputeShaderStats *frame_compute_stats = 0; BeginTextureMode(ctx->fsctx.output); ClearBackground(PINK); BeginShaderMode(ctx->fsctx.shader); @@ -800,11 +823,13 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) glUseProgram(fs->shader.id); u32 out_texture = 0; if (bp->output_points.w > 1) { - frame_to_draw = &ctx->averaged_frame; - out_texture = ctx->averaged_frame.texture; + frame_to_draw = &ctx->averaged_frame; + frame_compute_stats = &ctx->averaged_frame_compute_stats; + out_texture = ctx->averaged_frame.texture; } else { - frame_to_draw = ctx->beamform_frames + ctx->display_frame_index; - out_texture = frame_to_draw->ready_to_present ? frame_to_draw->texture : 0; + frame_to_draw = ctx->beamform_frames + ctx->display_frame_index; + frame_compute_stats = ctx->beamform_frame_compute_stats + ctx->display_frame_index; + out_texture = frame_to_draw->ready_to_present ? frame_to_draw->texture : 0; } glBindTextureUnit(0, out_texture); glUniform1f(fs->db_cutoff_id, fs->db); @@ -822,7 +847,7 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) ctx->fsctx.gen_mipmaps = 0; } - draw_ui(ctx, input, frame_to_draw); + draw_ui(ctx, input, frame_to_draw, frame_compute_stats); if (WindowShouldClose()) ctx->should_exit = 1; diff --git a/beamformer.h b/beamformer.h @@ -10,8 +10,6 @@ #include "util.h" -#define INFO_COLUMN_WIDTH 480 - enum gl_vendor_ids { GL_VENDOR_AMD, GL_VENDOR_ARM, @@ -123,6 +121,15 @@ DAS_TYPES } DASShaderID; typedef struct { + /* TODO(rnp): there is assumption here that each shader will occur only once + * per compute. add an insertion index and change these to hold the max number + * of executed compute stages */ + u32 timer_ids[CS_LAST]; + f32 times[CS_LAST]; + b32 timer_active[CS_LAST]; +} ComputeShaderStats; + +typedef struct { uv3 dim; u32 texture; @@ -136,11 +143,6 @@ typedef struct { b32 ready_to_present; DASShaderID das_shader_id; u32 compound_count; - - /* TODO(rnp): move this out so that saved frame copies can save some space */ - u32 timer_ids[CS_LAST]; - f32 compute_times[CS_LAST]; - b32 timer_active[CS_LAST]; } BeamformFrame; typedef struct { @@ -171,15 +173,20 @@ typedef struct { } ComputeShaderReloadContext; typedef struct { - BeamformFrame *frame; - iptr file_handle; + BeamformFrame *store; + ComputeShaderStats *stats; +} BeamformerWorkFrame; + +typedef struct { + BeamformerWorkFrame frame; + iptr file_handle; } BeamformOutputFrameContext; /* NOTE: discriminated union based on type */ typedef struct { union { iptr file_handle; - BeamformFrame *frame; + BeamformerWorkFrame frame; BeamformOutputFrameContext output_frame_ctx; ComputeShaderReloadContext *reload_shader_ctx; }; @@ -217,12 +224,15 @@ typedef struct BeamformerCtx { /* TODO(rnp): this is nasty and should be removed */ b32 ui_read_params; - BeamformFrame beamform_frames[MAX_BEAMFORMED_SAVED_FRAMES]; + BeamformFrame beamform_frames[MAX_BEAMFORMED_SAVED_FRAMES]; + ComputeShaderStats beamform_frame_compute_stats[MAX_BEAMFORMED_SAVED_FRAMES]; u32 next_render_frame_index; u32 display_frame_index; /* NOTE: this will only be used when we are averaging */ - BeamformFrame averaged_frame; + BeamformFrame averaged_frame; + ComputeShaderStats averaged_frame_compute_stats; + ComputeShaderCtx csctx; FragmentShaderCtx fsctx; diff --git a/intrinsics.c b/intrinsics.c @@ -8,6 +8,7 @@ #define atomic_load(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE) #define atomic_and(ptr, n) __atomic_and_fetch(ptr, n, __ATOMIC_RELEASE) #define atomic_add(ptr, n) __atomic_add_fetch(ptr, n, __ATOMIC_RELEASE) +#define atomic_inc(ptr, n) __atomic_fetch_add(ptr, n, __ATOMIC_ACQ_REL) static FORCE_INLINE u32 clz_u32(u32 a) diff --git a/static.c b/static.c @@ -205,19 +205,6 @@ static FILE_WATCH_CALLBACK_FN(queue_compute_shader_reload) work->type = BW_RELOAD_SHADER; work->reload_shader_ctx = csr; beamform_work_queue_push_commit(ctx->beamform_work_queue); - if (ctx->platform.compute_worker.asleep && - ctx->beamform_frames[ctx->display_frame_index].ready_to_present) - { - BeamformWork *compute = beamform_work_queue_push(ctx->beamform_work_queue); - if (compute) { - compute->type = BW_COMPUTE; - compute->frame = ctx->beamform_frames + ctx->next_render_frame_index++; - compute->frame->ready_to_present = 0; - if (ctx->next_render_frame_index >= ARRAY_COUNT(ctx->beamform_frames)) - ctx->next_render_frame_index = 0; - beamform_work_queue_push_commit(ctx->beamform_work_queue); - } - } ctx->platform.wake_thread(ctx->platform.compute_worker.sync_handle); } return 1; @@ -281,7 +268,7 @@ setup_beamformer(BeamformerCtx *ctx, Arena *memory) 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); + SetWindowMinSize(840, ctx->window_size.h); /* NOTE: Gather information about the GPU */ get_gl_params(&ctx->gl, &ctx->error_stream); diff --git a/ui.c b/ui.c @@ -45,6 +45,26 @@ typedef struct v2_sll { v2 v; } v2_sll; +typedef struct Variable Variable; + +typedef enum { + RSD_VERTICAL, + RSD_HORIZONTAL, +} RegionSplitDirection; + +typedef struct { + Variable *left; + Variable *right; + f32 fraction; + RegionSplitDirection direction; +} RegionSplit; + +/* TODO(rnp): this should be refactored to not need a BeamformerCtx */ +typedef struct { + BeamformerCtx *ctx; + void *stats; +} ComputeStatsView; + typedef enum { VT_NULL, VT_B32, @@ -52,8 +72,11 @@ typedef enum { VT_I32, VT_GROUP, VT_BEAMFORMER_VARIABLE, - VT_BEAMFORMER_VIEW, + VT_BEAMFORMER_FRAME_VIEW, + VT_COMPUTE_STATS_VIEW, + VT_COMPUTE_LATEST_STATS_VIEW, VT_SCALE_BAR, + VT_UI_REGION_SPLIT, } VariableType; typedef enum { @@ -64,8 +87,6 @@ typedef enum { VG_V4, } VariableGroupType; -typedef struct Variable Variable; - typedef struct { Variable *first; Variable *last; @@ -79,11 +100,13 @@ typedef struct { struct Variable { s8 name; union { - void *generic; - VariableGroup group; - b32 b32; - i32 i32; - f32 f32; + void *generic; + VariableGroup group; + RegionSplit region_split; + ComputeStatsView compute_stats_view; + b32 b32; + i32 i32; + f32 f32; } u; Variable *next; Variable *parent; @@ -137,11 +160,12 @@ typedef enum { } BeamformerFrameViewType; typedef struct { - BeamformFrame *frame; + void *store; ScaleBar lateral_scale_bar; ScaleBar axial_scale_bar; Variable menu; - Rect display_rect; + /* TODO(rnp): hack, this needs to be removed to support multiple views */ + FragmentShaderCtx *ctx; BeamformerFrameViewType type; } BeamformerFrameView; @@ -153,8 +177,7 @@ typedef struct { } InteractionState; typedef struct { - TempArena frame_temporary_arena; - Arena arena; + Arena arena; Font font; Font small_font; @@ -165,13 +188,12 @@ typedef struct { InteractionState interaction; InputState text_input_state; - Variable *parameters_view; - - Variable beamform_views; + Variable *regions; v2_sll *scale_bar_savepoint_freelist; - BeamformFrame *latest_frame; + BeamformFrame *latest_frame; + ComputeShaderStats *latest_compute_stats; v2 ruler_start_p; v2 ruler_stop_p; @@ -257,7 +279,7 @@ add_variable(Variable *group, Arena *arena, s8 name, u32 flags, VariableType typ result->name = name; result->parent = group; - if (group) { + if (group && group->type == VT_GROUP) { if (group->u.group.last) group->u.group.last = group->u.group.last->next = result; else group->u.group.last = group->u.group.first = result; @@ -283,6 +305,16 @@ end_variable_group(Variable *group) return group->parent; } +static Variable * +add_ui_split(Variable *parent, Arena *arena, s8 name, f32 fraction, + RegionSplitDirection direction, Font font) +{ + Variable *result = add_variable(parent, arena, name, 0, VT_UI_REGION_SPLIT, font); + result->u.region_split.direction = direction; + result->u.region_split.fraction = fraction; + return result; +} + static void add_beamformer_variable_f32(Variable *group, Arena *arena, s8 name, s8 suffix, f32 *store, v2 limits, f32 display_scale, f32 scroll_scale, u32 flags, Font font) @@ -311,89 +343,88 @@ add_beamformer_variable_b32(Variable *group, Arena *arena, s8 name, s8 false_tex bv->name_table.names[1] = true_text; } -static void -beamformer_parameters_view_init(BeamformerCtx *ctx) +static Variable * +add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) { BeamformerUI *ui = ctx->ui; BeamformerUIParameters *bp = &ui->params; v2 v2_inf = {.x = -F32_INFINITY, .y = F32_INFINITY}; - Variable *group = add_variable_group(0, &ui->arena, s8("Parameters List"), VG_LIST, ui->font); - ui->parameters_view = group; + Variable *result = add_variable_group(parent, &ui->arena, s8("Parameters List"), VG_LIST, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Sampling Frequency:"), s8("[MHz]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Sampling Frequency:"), s8("[MHz]"), &bp->sampling_frequency, (v2){0}, 1e-6, 0, 0, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Center Frequency:"), s8("[MHz]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Center Frequency:"), s8("[MHz]"), &bp->center_frequency, (v2){.y = 100e-6}, 1e-6, 1e5, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Speed of Sound:"), s8("[m/s]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Speed of Sound:"), s8("[m/s]"), &bp->speed_of_sound, (v2){.y = 1e6}, 1, 10, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - group = add_variable_group(group, &ui->arena, s8("Lateral Extent:"), VG_V2, ui->font); + result = add_variable_group(result, &ui->arena, s8("Lateral Extent:"), VG_V2, ui->font); { - add_beamformer_variable_f32(group, &ui->arena, s8("Min:"), s8("[mm]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Min:"), s8("[mm]"), &bp->output_min_coordinate.x, v2_inf, 1e3, 0.5e-3, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Max:"), s8("[mm]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Max:"), s8("[mm]"), &bp->output_max_coordinate.x, v2_inf, 1e3, 0.5e-3, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); } - group = end_variable_group(group); + result = end_variable_group(result); - group = add_variable_group(group, &ui->arena, s8("Axial Extent:"), VG_V2, ui->font); + result = add_variable_group(result, &ui->arena, s8("Axial Extent:"), VG_V2, ui->font); { - add_beamformer_variable_f32(group, &ui->arena, s8("Min:"), s8("[mm]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Min:"), s8("[mm]"), &bp->output_min_coordinate.z, v2_inf, 1e3, 0.5e-3, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Max:"), s8("[mm]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Max:"), s8("[mm]"), &bp->output_max_coordinate.z, v2_inf, 1e3, 0.5e-3, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); } - group = end_variable_group(group); + result = end_variable_group(result); - add_beamformer_variable_f32(group, &ui->arena, s8("Off Axis Position:"), s8("[mm]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Off Axis Position:"), s8("[mm]"), &bp->off_axis_pos, (v2){.x = -1e3, .y = 1e3}, 1e3, 0.5e-3, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - add_beamformer_variable_b32(group, &ui->arena, s8("Beamform Plane:"), s8("XZ"), s8("YZ"), + add_beamformer_variable_b32(result, &ui->arena, s8("Beamform Plane:"), s8("XZ"), s8("YZ"), (b32 *)&bp->beamform_plane, V_INPUT|V_CAUSES_COMPUTE, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("F#:"), s8(""), &bp->f_number, + add_beamformer_variable_f32(result, &ui->arena, s8("F#:"), s8(""), &bp->f_number, (v2){.y = 1e3}, 1, 0.1, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Dynamic Range:"), s8("[dB]"), + add_beamformer_variable_f32(result, &ui->arena, s8("Dynamic Range:"), s8("[dB]"), &ctx->fsctx.db, (v2){.x = -120}, 1, 1, V_INPUT|V_TEXT|V_GEN_MIPMAPS, ui->font); - add_beamformer_variable_f32(group, &ui->arena, s8("Threshold:"), s8(""), + add_beamformer_variable_f32(result, &ui->arena, s8("Threshold:"), s8(""), &ctx->fsctx.threshold, (v2){.y = 240}, 1, 1, V_INPUT|V_TEXT|V_GEN_MIPMAPS, ui->font); - group = end_variable_group(group); - ASSERT(group == 0); + return result; } -static BeamformerFrameView * -beamformer_frame_view_new(BeamformerUI *ui, BeamformerFrameViewType type) +static Variable * +add_beamformer_frame_view(Variable *parent, Arena *arena, BeamformerFrameViewType type, Font font) { - BeamformerFrameView *result = push_struct(&ui->arena, typeof(*result)); - result->type = type; + Variable *result = add_variable(parent, arena, s8(""), 0, VT_BEAMFORMER_FRAME_VIEW, font); + BeamformerFrameView *bv = result->u.generic = push_struct(arena, typeof(*bv)); + bv->type = type; return result; } static BeamformFrame * -beamformer_frame_view_frame(BeamformerUI *ui, BeamformerFrameView *bv) +beamformer_frame_view_frame(BeamformerFrameView *bv) { BeamformFrame *result; switch (bv->type) { - case FVT_LATEST: result = ui->latest_frame; break; - default: result = bv->frame; break; + case FVT_LATEST: result = *(BeamformFrame **)bv->store; break; + default: result = bv->store; break; } return result; } @@ -645,16 +676,15 @@ do_scale_bar(BeamformerUI *ui, Stream *buf, ScaleBar *sb, ScaleBarDirection dire } static void -draw_beamform_view(BeamformerCtx *ctx, Arena a, v2 mouse, BeamformerFrameView *view) +draw_beamformer_frame_view(BeamformerUI *ui, Arena a, BeamformerFrameView *view, + Rect display_rect, v2 mouse) { - BeamformerUI *ui = ctx->ui; BeamformerUIParameters *bp = &ui->params; InteractionState *is = &ui->interaction; - Rect display_rect = view->display_rect; - BeamformFrame *frame = beamformer_frame_view_frame(ui, view); + BeamformFrame *frame = beamformer_frame_view_frame(view); Stream buf = arena_stream(&a); - Texture *output = &ctx->fsctx.output.texture; + Texture *output = &view->ctx->output.texture; v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); @@ -726,7 +756,7 @@ draw_beamform_view(BeamformerCtx *ctx, Arena a, v2 mouse, BeamformerFrameView *v if (CheckCollisionPointRec(mouse.rl, vr.rl)) { BeamformerVariable *bv = zero_struct(ui->scratch_variable); bv->base.type = VT_BEAMFORMER_VARIABLE; - bv->base.u.generic = &ctx->fsctx.threshold; + bv->base.u.generic = &view->ctx->threshold; bv->base.flags = V_GEN_MIPMAPS; bv->subtype = VT_F32; bv->params.limits = (v2){.y = 240}; @@ -826,9 +856,13 @@ draw_beamformer_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 static Rect draw_variable_list(BeamformerUI *ui, Variable *group, Rect r, v2 mouse) { + /* TODO(rnp): limit the width of each element so that elements don't overlap */ ASSERT(group->type == VT_GROUP); + r.pos.x += LISTING_INDENT; r.size.x -= LISTING_INDENT; + r.pos.y += LISTING_INDENT; + r.size.y -= LISTING_INDENT; Variable *var = group->u.group.first; f32 x_off = group->u.group.max_name_width; @@ -913,7 +947,7 @@ draw_variable_list(BeamformerUI *ui, Variable *group, Rect r, v2 mouse) } static void -draw_debug_overlay(BeamformerCtx *ctx, BeamformFrame *frame, Arena arena, Rect r) +draw_compute_stats(BeamformerCtx *ctx, ComputeShaderStats *stats, Arena arena, Rect r) { static s8 labels[CS_LAST] = { #define X(e, n, s, h, pn) [CS_##e] = s8(pn ":"), @@ -922,10 +956,12 @@ draw_debug_overlay(BeamformerCtx *ctx, BeamformFrame *frame, Arena arena, Rect r }; BeamformerUI *ui = ctx->ui; - uv2 ws = ctx->window_size; Stream buf = stream_alloc(&arena, 64); - v2 pos = {.x = 20, .y = ws.h - 10}; + + v2 pos; + pos.x = r.pos.x + LISTING_INDENT; + pos.y = r.pos.y + r.size.h - LISTING_INDENT; f32 compute_time_sum = 0; u32 stages = ctx->params->compute_stages_count; @@ -935,13 +971,13 @@ draw_debug_overlay(BeamformerCtx *ctx, BeamformFrame *frame, Arena arena, Rect r draw_text(ui->font, labels[index], pos, colour_from_normalized(FG_COLOUR)); buf.widx = 0; - stream_append_f64_e(&buf, frame->compute_times[index]); + stream_append_f64_e(&buf, stats->times[index]); stream_append_s8(&buf, s8(" [s]")); v2 txt_fs = measure_text(ui->font, stream_to_s8(&buf)); v2 rpos = {.x = r.pos.x + r.size.w - txt_fs.w, .y = pos.y}; draw_text(ui->font, stream_to_s8(&buf), rpos, colour_from_normalized(FG_COLOUR)); - compute_time_sum += frame->compute_times[index]; + compute_time_sum += stats->times[index]; } pos.y -= ui->font.baseSize; @@ -1017,6 +1053,78 @@ draw_active_text_box(BeamformerUI *ui, Variable *var) } static void +draw_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) +{ + switch (var->type) { + case VT_GROUP: { + draw_variable_list(ui, var, draw_rect, mouse); + } break; + case VT_BEAMFORMER_FRAME_VIEW: { + BeamformerFrameView *view = var->u.generic; + if (beamformer_frame_view_frame(view)->ready_to_present) + draw_beamformer_frame_view(ui, ui->arena, view, draw_rect, mouse); + } break; + case VT_COMPUTE_LATEST_STATS_VIEW: { + ComputeStatsView *csv = &var->u.compute_stats_view; + draw_compute_stats(csv->ctx, *(ComputeShaderStats **)csv->stats, ui->arena, draw_rect); + } break; + case VT_COMPUTE_STATS_VIEW: { + ComputeStatsView *csv = &var->u.compute_stats_view; + draw_compute_stats(csv->ctx, csv->stats, ui->arena, draw_rect); + } break; + default: break; + } +} + +static void +draw_ui_regions(BeamformerUI *ui, Rect window, v2 mouse) +{ + struct region_stack_item { + Variable *var; + Rect rect; + } *region_stack; + + TempArena arena_savepoint = begin_temp_arena(&ui->arena); + i32 stack_index = 0; + + region_stack = alloc(&ui->arena, typeof(*region_stack), 256); + region_stack[0].var = ui->regions; + region_stack[0].rect = window; + + while (stack_index != -1) { + struct region_stack_item *rsi = region_stack + stack_index--; + Rect rect = rsi->rect; + switch (rsi->var->type) { + case VT_UI_REGION_SPLIT: { + Rect first, second; + RegionSplit *rs = &rsi->var->u.region_split; + switch (rs->direction) { + case RSD_VERTICAL: + split_rect_vertical(rect, rs->fraction, &first, &second); + break; + case RSD_HORIZONTAL: + split_rect_horizontal(rect, rs->fraction, &first, &second); + break; + }; + stack_index++; + region_stack[stack_index].var = rs->right; + region_stack[stack_index].rect = second; + stack_index++; + region_stack[stack_index].var = rs->left; + region_stack[stack_index].rect = first; + /* TODO(rnp): draw the split */ + } break; + default: { + draw_variable(ui, rsi->var, rect, mouse); + } break; + } + ASSERT(stack_index < 256); + } + + end_temp_arena(arena_savepoint); +} + +static void ui_store_variable_base(VariableType type, void *store, void *new_value, void *limits) { switch (type) { @@ -1318,8 +1426,7 @@ ui_end_interact(BeamformerCtx *ctx, v2 mouse) is->active->u.b32 = !is->active->u.b32; } break; case VT_BEAMFORMER_VARIABLE: { - BeamformerVariable *bv = (BeamformerVariable *)is->active; - ASSERT(bv->subtype == VT_B32); + ASSERT(((BeamformerVariable *)is->active)->subtype == VT_B32); b32 *val = is->active->u.generic; *val = !(*val); } break; @@ -1403,20 +1510,22 @@ ui_init(BeamformerCtx *ctx, Arena store) ui = ctx->ui = push_struct(&store, typeof(*ui)); ui->arena = store; - ui->frame_temporary_arena = begin_temp_arena(&ui->arena); /* TODO: build these into the binary */ ui->font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 28, 0, 0); ui->small_font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 22, 0, 0); - beamformer_parameters_view_init(ctx); ui->scratch_variable = ui->scratch_variables + 0; - - /* NOTE(rnp): by default we always have at least one view */ - Variable *var = add_variable(&ui->beamform_views, &ui->arena, s8("Beamformed Views"), 0, - VT_BEAMFORMER_VIEW, ui->font); - BeamformerFrameView *bv = var->u.generic = beamformer_frame_view_new(ui, FVT_LATEST); + Variable *split = ui->regions = add_ui_split(0, &ui->arena, s8("UI Root"), 0.35, + RSD_HORIZONTAL, ui->font); + split->u.region_split.left = add_ui_split(split, &ui->arena, s8(""), 0.8, + RSD_VERTICAL, ui->font); + split->u.region_split.right = add_beamformer_frame_view(split, &ui->arena, + FVT_LATEST, ui->font); + + BeamformerFrameView *bv = split->u.region_split.right->u.generic; + bv->store = &ui->latest_frame; bv->lateral_scale_bar.min_value = &ui->params.output_min_coordinate.x; bv->lateral_scale_bar.max_value = &ui->params.output_max_coordinate.x; bv->axial_scale_bar.min_value = &ui->params.output_min_coordinate.z; @@ -1431,6 +1540,17 @@ ui_init(BeamformerCtx *ctx, Arena store) bv->lateral_scale_bar.zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; bv->axial_scale_bar.zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; + /* TODO(rnp): hack: each view needs their own cut down fragment shader context */ + bv->ctx = &ctx->fsctx; + + split = split->u.region_split.left; + split->u.region_split.left = add_beamformer_parameters_view(split, ctx); + split->u.region_split.right = add_variable(split, &ui->arena, s8(""), 0, + VT_COMPUTE_LATEST_STATS_VIEW, ui->font); + ComputeStatsView *compute_stats = &split->u.region_split.right->u.compute_stats_view; + compute_stats->ctx = ctx; + compute_stats->stats = &ui->latest_compute_stats; + ctx->ui_read_params = 1; } @@ -1444,15 +1564,12 @@ validate_ui_parameters(BeamformerUIParameters *p) } static void -draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw) +draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw, + ComputeShaderStats *latest_compute_stats) { BeamformerUI *ui = ctx->ui; ui->latest_frame = frame_to_draw; - - /* TODO(rnp): we need an ALLOC_END flag so that we can have permanent storage - * or we need a sub arena for the save point stack */ - //end_temp_arena(ui->frame_temporary_arena); - //ui->frame_temporary_arena = begin_temp_arena(&ui->arena); + ui->latest_compute_stats = latest_compute_stats; /* TODO(rnp): there should be a better way of detecting this */ if (ctx->ui_read_params) { @@ -1478,26 +1595,10 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw BeginDrawing(); ClearBackground(colour_from_normalized(BG_COLOUR)); - v2 mouse = input->mouse; - Rect wr = {.size = {.w = (f32)ctx->window_size.w, .h = (f32)ctx->window_size.h}}; - Rect lr = wr, rr = wr; - lr.size.w = INFO_COLUMN_WIDTH; - rr.size.w = wr.size.w - lr.size.w; - rr.pos.x = lr.pos.x + lr.size.w; - - { - Rect draw_r = lr; - draw_r.pos.y += 20; - draw_r.size.y -= 20; - draw_variable_list(ui, ui->parameters_view, draw_r, mouse); - } - if (ui->latest_frame->ready_to_present) { - BeamformerFrameView *view = ui->beamform_views.u.group.first->u.generic; - view->display_rect = rr; - draw_beamform_view(ctx, ui->arena, mouse, view); - } - draw_debug_overlay(ctx, frame_to_draw, ui->arena, lr); + v2 mouse = input->mouse; + Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; + draw_ui_regions(ui, window_rect, mouse); if (ui->interaction.type == IT_TEXT) draw_active_text_box(ui, ui->interaction.active); EndDrawing(); diff --git a/util.c b/util.c @@ -549,6 +549,38 @@ sub_v4(v4 a, v4 b) return result; } +static void +split_rect_horizontal(Rect rect, f32 fraction, Rect *left, Rect *right) +{ + if (left) { + left->pos = rect.pos; + left->size.h = rect.size.h; + left->size.w = rect.size.w * fraction; + } + if (right) { + right->pos = rect.pos; + right->pos.x += rect.size.w * fraction; + right->size.h = rect.size.h; + right->size.w = rect.size.w * (1.0f - fraction); + } +} + +static void +split_rect_vertical(Rect rect, f32 fraction, Rect *top, Rect *bot) +{ + if (top) { + top->pos = rect.pos; + top->size.w = rect.size.w; + top->size.h = rect.size.h * fraction; + } + if (bot) { + bot->pos = rect.pos; + bot->pos.y += rect.size.h * fraction; + bot->size.w = rect.size.w; + bot->size.h = rect.size.h * (1.0f - fraction); + } +} + static f64 parse_f64(s8 s) {