Commit: 47ae9c26a59520dc11837c2b0da98a06975a82af
Parent: c60092a6a362c5431fe7e5ae5d8b00294e82cee4
Author: Randy Palamar
Date: Sat, 15 Mar 2025 22:07:05 -0600
ui: implement a region based layout system
Diffstat:
M | beamformer.c | | | 169 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
M | beamformer.h | | | 34 | ++++++++++++++++++++++------------ |
M | intrinsics.c | | | 1 | + |
M | static.c | | | 15 | +-------------- |
M | ui.c | | | 283 | +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- |
M | util.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)
{