Commit: ac7b64eba8356574a00c77fbf623bfef6d4efd67
Parent: e8d3608fe43b5c528a5c9d625bb0247f97d4d149
Author: Randy Palamar
Date: Tue, 25 Mar 2025 06:11:09 -0600
ui: add frame view splits
In this first pass existing frames can be copied. Filtering based
on tag will be added later.
closes: #25
Diffstat:
M | beamformer.c | | | 45 | ++++++++++++++++++++++++--------------------- |
M | beamformer.h | | | 5 | ++++- |
M | static.c | | | 3 | +++ |
M | ui.c | | | 366 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
M | util.c | | | 18 | ++++++++++++++++++ |
M | util.h | | | 11 | +++++++++++ |
6 files changed, 338 insertions(+), 110 deletions(-)
diff --git a/beamformer.c b/beamformer.c
@@ -59,34 +59,40 @@ frame_next(BeamformFrameIterator *bfi)
static void
alloc_beamform_frame(GLParams *gp, BeamformFrame *out, ComputeShaderStats *out_stats,
- uv3 out_dim, u32 frame_index, s8 name)
+ uv3 out_dim, s8 name, Arena arena)
{
out->ready_to_present = 0;
- 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);
- out->dim.z = CLAMP(round_down_power_of_2(ORONE(out_dim.z)), 1, gp->max_3d_texture_dim);
+ out->dim.x = MAX(1, round_down_power_of_2(ORONE(out_dim.x)));
+ out->dim.y = MAX(1, round_down_power_of_2(ORONE(out_dim.y)));
+ out->dim.z = MAX(1, round_down_power_of_2(ORONE(out_dim.z)));
+
+ if (gp) {
+ out->dim.x = MIN(out->dim.x, gp->max_3d_texture_dim);
+ out->dim.y = MIN(out->dim.y, gp->max_3d_texture_dim);
+ out->dim.z = MIN(out->dim.z, gp->max_3d_texture_dim);
+ }
/* NOTE: allocate storage for beamformed output data;
* this is shared between compute and fragment shaders */
u32 max_dim = MAX(out->dim.x, MAX(out->dim.y, out->dim.z));
out->mips = ctz_u32(max_dim) + 1;
- /* TODO(rnp): arena?? */
- u8 buf[256];
- Stream label = {.data = buf, .cap = ARRAY_COUNT(buf)};
+ Stream label = arena_stream(&arena);
stream_append_s8(&label, name);
stream_append_byte(&label, '[');
- stream_append_u64(&label, frame_index);
- stream_append_s8(&label, s8("]"));
+ stream_append_hex_u64(&label, out->id);
+ stream_append_byte(&label, ']');
glDeleteTextures(1, &out->texture);
glCreateTextures(GL_TEXTURE_3D, 1, &out->texture);
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_stats->timer_ids), out_stats->timer_ids);
- glCreateQueries(GL_TIME_ELAPSED, ARRAY_COUNT(out_stats->timer_ids), out_stats->timer_ids);
+ if (out_stats) {
+ 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
@@ -220,12 +226,13 @@ 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);
+ u32 frame_id = atomic_inc(&ctx->next_render_frame_index, 1);
+ u32 frame_index = frame_id % 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;
+ work->frame.store->id = frame_id;
}
return result;
}
@@ -638,22 +645,18 @@ DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute)
glProgramUniform1ui(cs->programs[CS_DAS], cs->cycle_t_id, cycle_t++);
uv3 try_dim = make_valid_test_dim(ctx->params->raw.output_points.xyz);
- if (!uv3_equal(try_dim, frame->store->dim)) {
- iz frame_index = frame->store - ctx->beamform_frames;
+ if (!uv3_equal(try_dim, frame->store->dim))
alloc_beamform_frame(&ctx->gl, frame->store, frame->stats, try_dim,
- frame_index, s8("Beamformed_Data"));
- }
+ s8("Beamformed_Data"), arena);
if (ctx->params->raw.output_points.w > 1) {
if (!uv3_equal(try_dim, ctx->averaged_frames[0].dim)) {
alloc_beamform_frame(&ctx->gl, ctx->averaged_frames + 0,
ctx->averaged_frame_compute_stats + 0,
- try_dim, 0,
- s8("Beamformed_Averaged_Data"));
+ try_dim, s8("Averaged Frame"), arena);
alloc_beamform_frame(&ctx->gl, ctx->averaged_frames + 1,
ctx->averaged_frame_compute_stats + 1,
- try_dim, 1,
- s8("Beamformed_Averaged_Data"));
+ try_dim, s8("Averaged Frame"), arena);
}
}
diff --git a/beamformer.h b/beamformer.h
@@ -126,7 +126,7 @@ typedef struct {
b32 timer_active[CS_LAST];
} ComputeShaderStats;
-typedef struct {
+typedef struct BeamformFrame {
uv3 dim;
u32 texture;
@@ -140,6 +140,9 @@ typedef struct {
b32 ready_to_present;
DASShaderID das_shader_id;
u32 compound_count;
+ u32 id;
+
+ struct BeamformFrame *next;
} BeamformFrame;
typedef struct {
diff --git a/static.c b/static.c
@@ -295,6 +295,9 @@ setup_beamformer(BeamformerCtx *ctx, Arena *memory)
ctx->beamform_work_queue = push_struct(memory, BeamformWorkQueue);
+ ctx->averaged_frames[0].id = 0;
+ ctx->averaged_frames[1].id = 1;
+
ctx->params = os_open_shared_memory_area(OS_SMEM_NAME, sizeof(*ctx->params));
/* TODO: properly handle this? */
ASSERT(ctx->params);
diff --git a/ui.c b/ui.c
@@ -1,18 +1,23 @@
/* See LICENSE for license details. */
/* TODO(rnp):
- * [ ]: split frame views
- * [ ]: scroll bar for views don't have enough space
+ * [ ]: scroll bar for views that don't have enough space
* [ ]: compute times through same path as parameter list ?
* [ ]: global menu
* [ ]: allow views to collapse to just their title bar
- * [ ]: enforce a mininum region size or allow regions themselves to scroll
+ * [ ]: enforce a minimum region size or allow regions themselves to scroll
+ * [ ]: allow scale bars to collapse
* [ ]: move remaining fragment shader stuff into ui
* [ ]: refactor: draw_left_aligned_list()
* [ ]: refactor: draw_variable_table()
* [ ]: refactor: add_variable_no_link()
- * [ ]: fix leaking in ui_variable_free for UIView (when close, menu are populated)
* [ ]: refactor: draw_text with flags (OUTLINED|ROTATED|LIMITED|etc..)
* [ ]: refactor: draw_text_limited should clamp to rect and measure text itself
+ * [ ]: refactor: draw_active_menu should just use draw_variable_list
+ * [ ]: refactor: draw_beamformer_variable -> draw_variable; draw_variable -> draw_layout_variable
+ * [ ]: refactor: scale bars should just be variables
+ * [ ]: refactor: remove scale bar limits (limits should only prevent invalid program state)
+ * [ ]: ui leaks split beamform views on hot-reload
+ * [ ]: add tag based selection to frame views
*/
#define BG_COLOUR (v4){.r = 0.15, .g = 0.12, .b = 0.13, .a = 1.0}
@@ -21,6 +26,9 @@
#define HOVERED_COLOUR (v4){.r = 0.11, .g = 0.50, .b = 0.59, .a = 1.0}
#define RULER_COLOUR (v4){.r = 1.00, .g = 0.70, .b = 0.00, .a = 1.0}
+#define MENU_PLUS_COLOUR (v4){.r = 0.33, .g = 0.42, .b = 1.00, .a = 1.0}
+#define MENU_CLOSE_COLOUR FOCUSED_COLOUR
+
#define NORMALIZED_FG_COLOUR colour_from_normalized(FG_COLOUR)
#define HOVER_SPEED 5.0f
@@ -49,16 +57,21 @@ typedef struct {
Font *font, *hot_font;
} InputState;
+typedef struct {
+ v2 at;
+ Font *font, *hot_font;
+} MenuState;
+
typedef enum {
IT_NONE,
IT_NOP,
- IT_SET,
+ IT_DISPLAY,
IT_DRAG,
+ IT_MENU,
+ IT_SCALE_BAR,
IT_SCROLL,
+ IT_SET,
IT_TEXT,
-
- IT_DISPLAY,
- IT_SCALE_BAR,
} InteractionType;
enum ruler_state {
@@ -72,6 +85,7 @@ typedef struct v2_sll {
v2 v;
} v2_sll;
+typedef struct BeamformerUI BeamformerUI;
typedef struct Variable Variable;
typedef enum {
@@ -111,6 +125,7 @@ typedef enum {
VT_COMPUTE_LATEST_STATS_VIEW,
VT_COMPUTE_PROGRESS_BAR,
VT_SCALE_BAR,
+ VT_UI_BUTTON,
VT_UI_VIEW,
VT_UI_REGION_SPLIT,
VT_UI_REGION_CLOSE,
@@ -162,10 +177,14 @@ typedef struct {
VariableType store_type;
} BeamformerVariable;
+#define UI_BUTTON_FN(name) void name(BeamformerUI *ui, Variable *var)
+typedef UI_BUTTON_FN(ui_button_fn);
+
enum variable_flags {
V_INPUT = 1 << 0,
V_TEXT = 1 << 1,
V_BUTTON = 1 << 2,
+ V_MENU = 1 << 3,
V_CAUSES_COMPUTE = 1 << 29,
V_UPDATE_VIEW = 1 << 30,
};
@@ -180,6 +199,7 @@ struct Variable {
RegionSplit region_split;
UIView view;
VariableGroup group;
+ ui_button_fn *button;
b32 b32;
i32 i32;
f32 f32;
@@ -207,6 +227,7 @@ typedef struct {
v2 limits;
v2 scroll_scale;
f32 hover_t;
+ b32 causes_compute;
} ScaleBar;
typedef enum {
@@ -215,7 +236,7 @@ typedef enum {
FVT_COPY,
} BeamformerFrameViewType;
-typedef struct {
+typedef struct BeamformerFrameView {
ScaleBar lateral_scale_bar;
ScaleBar axial_scale_bar;
@@ -225,10 +246,9 @@ typedef struct {
Variable threshold;
Variable dynamic_range;
- /* TODO(rnp): hack, this needs to be removed to support multiple views */
FragmentShaderCtx *ctx;
BeamformFrame *frame;
- void *next;
+ struct BeamformerFrameView *prev, *next;
RenderTexture2D rendered_view;
BeamformerFrameViewType type;
@@ -242,7 +262,7 @@ typedef struct {
InteractionType type;
} InteractionState;
-typedef struct {
+struct BeamformerUI {
Arena arena;
Font font;
@@ -255,13 +275,15 @@ typedef struct {
BeamformerFrameView *views;
BeamformerFrameView *view_freelist;
+ BeamformFrame *frame_freelist;
InteractionState interaction;
+ /* TODO(rnp): these can be combined */
+ MenuState menu_state;
InputState text_input_state;
v2_sll *scale_bar_savepoint_freelist;
-
v2 ruler_start_p;
v2 ruler_stop_p;
u32 ruler_state;
@@ -274,7 +296,9 @@ typedef struct {
b32 flush_params;
iptr last_displayed_frame;
-} BeamformerUI;
+
+ OS *os;
+};
static v2
measure_text(Font font, s8 text)
@@ -310,6 +334,7 @@ static void
stream_append_variable(Stream *s, Variable *var)
{
switch (var->type) {
+ case VT_UI_BUTTON:
case VT_GROUP: stream_append_s8(s, var->name); break;
case VT_BEAMFORMER_VARIABLE: {
BeamformerVariable *bv = &var->u.beamformer_variable;
@@ -335,7 +360,33 @@ stream_append_variable(Stream *s, Variable *var)
}
}
-/* TODO(rnp): this function leaks for UIView */
+static void
+resize_frame_view(BeamformerFrameView *view, uv2 dim)
+{
+ UnloadRenderTexture(view->rendered_view);
+ /* TODO(rnp): sometimes when accepting data on w32 something happens
+ * and the program will stall in vprintf in TraceLog(...) here.
+ * for now do this to avoid the problem */
+ //SetTraceLogLevel(LOG_NONE);
+ view->rendered_view = LoadRenderTexture(dim.x, dim.y);
+ //SetTraceLogLevel(LOG_INFO);
+
+ /* TODO(rnp): add some ID for the specific view here */
+ LABEL_GL_OBJECT(GL_FRAMEBUFFER, view->rendered_view.id, s8("Frame View"));
+ LABEL_GL_OBJECT(GL_TEXTURE, view->rendered_view.texture.id, s8("Frame View Texture"));
+ glGenerateTextureMipmap(view->rendered_view.texture.id);
+
+ //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_ANISOTROPIC_8X);
+ //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_TRILINEAR);
+ //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_BILINEAR);
+
+ /* NOTE(rnp): work around raylib's janky texture sampling */
+ i32 id = view->rendered_view.texture.id;
+ glTextureParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTextureParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTextureParameterfv(id, GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1});
+}
+
static void
ui_variable_free(BeamformerUI *ui, Variable *var)
{
@@ -348,7 +399,14 @@ ui_variable_free(BeamformerUI *ui, Variable *var)
if (var->type == VT_BEAMFORMER_FRAME_VIEW) {
/* TODO(rnp): instead there should be a way of linking these up */
BeamformerFrameView *bv = var->u.generic;
- SLLPush(bv, ui->view_freelist);
+ glDeleteTextures(1, &bv->frame->texture);
+ bv->frame->texture = 0;
+ DLLRemove(bv);
+ /* TODO(rnp): hack; use a sentinal */
+ if (bv == ui->views)
+ ui->views = bv->next;
+ SLLPush(bv->frame, ui->frame_freelist);
+ SLLPush(bv, ui->view_freelist);
}
Variable *next = SLLPush(var, ui->variable_freelist);
@@ -419,6 +477,14 @@ end_variable_group(Variable *group)
}
static Variable *
+add_button(BeamformerUI *ui, Variable *group, Arena *arena, s8 name, ui_button_fn *function, Font font)
+{
+ Variable *result = add_variable(ui, group, arena, name, V_INPUT, VT_UI_BUTTON, font);
+ result->u.button = function;
+ return result;
+}
+
+static Variable *
add_ui_split(BeamformerUI *ui, Variable *parent, Arena *arena, s8 name, f32 fraction,
RegionSplitDirection direction, Font font)
{
@@ -531,24 +597,49 @@ add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx)
return result;
}
+static UI_BUTTON_FN(ui_export_frame);
+static UI_BUTTON_FN(ui_copy_frame_horizontal);
+static UI_BUTTON_FN(ui_copy_frame_vertical);
+
static Variable *
-add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, BeamformerFrameViewType type)
+add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena,
+ BeamformerFrameViewType type, b32 closable)
{
- /* TODO(rnp): this can be closable once we have a way of opening new views */
- Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, 0);
- Variable *var = add_variable(ui, result, &ui->arena, s8(""), 0, VT_BEAMFORMER_FRAME_VIEW,
- ui->small_font);
+ /* TODO(rnp): this can be always closable once we have a way of opening new views */
+ Variable *result = add_ui_view(ui, parent, arena, s8(""), UI_VIEW_CUSTOM_TEXT, closable);
+ Variable *menu = result->u.view.menu = add_variable_group(ui, 0, &ui->arena, s8(""),
+ VG_LIST, ui->small_font);
+ menu->flags = V_MENU;
+ menu->parent = result;
+ add_button(ui, menu, &ui->arena, s8("Copy Horizontal"), ui_copy_frame_horizontal, ui->small_font);
+ add_button(ui, menu, &ui->arena, s8("Copy Vertical"), ui_copy_frame_vertical, ui->small_font);
+
+ Variable *var = add_variable(ui, result, &ui->arena, s8(""), 0, VT_BEAMFORMER_FRAME_VIEW,
+ ui->small_font);
+
+ BeamformerFrameView *bv = SLLPop(ui->view_freelist);
+ if (bv) zero_struct(bv);
+ else bv = push_struct(arena, typeof(*bv));
+ DLLPushDown(bv, ui->views);
+
+ var->u.generic = bv;
- BeamformerFrameView *bv = var->u.generic = push_struct(arena, typeof(*bv));
- bv->type = type;
- SLLPush(bv, ui->views);
fill_variable(&bv->dynamic_range, var, s8("Dynamic Range:"), V_INPUT|V_TEXT|V_UPDATE_VIEW,
VT_F32, ui->small_font);
fill_variable(&bv->threshold, var, s8("Threshold:"), V_INPUT|V_TEXT|V_UPDATE_VIEW,
VT_F32, ui->small_font);
+
+ bv->type = type;
bv->dynamic_range.u.f32 = -50.0f;
bv->threshold.u.f32 = 40.0f;
+ bv->lateral_scale_bar.limits = (v2){.x = -1, .y = 1};
+ bv->axial_scale_bar.limits = (v2){.x = 0, .y = 1};
+ bv->lateral_scale_bar.scroll_scale = (v2){.x = -0.5e-3, .y = 0.5e-3};
+ bv->axial_scale_bar.scroll_scale = (v2){.x = 0, .y = 1e-3};
+ 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};
+
return result;
}
@@ -576,32 +667,64 @@ add_compute_stats_view(BeamformerUI *ui, Variable *parent, Arena *arena, Variabl
}
static void
-resize_frame_view(BeamformerFrameView *view, uv2 dim)
+ui_copy_frame_base(BeamformerUI *ui, Variable *button, RegionSplitDirection direction)
{
- UnloadRenderTexture(view->rendered_view);
- /* TODO(rnp): sometimes when accepting data on w32 something happens
- * and the program will stall in vprintf in TraceLog(...) here.
- * for now do this to avoid the problem */
- //SetTraceLogLevel(LOG_NONE);
- view->rendered_view = LoadRenderTexture(dim.x, dim.y);
- //SetTraceLogLevel(LOG_INFO);
+ Variable *menu = button->parent;
+ Variable *view = menu->parent;
+ Variable *region = view->parent;
+ ASSERT(region->type == VT_UI_REGION_SPLIT);
+ ASSERT(view->type == VT_UI_VIEW);
+
+ BeamformerFrameView *old = view->u.group.first->u.generic;
+ /* TODO(rnp): hack; it would be better if this was unreachable with a 0 old->frame */
+ if (!old->frame)
+ return;
- /* TODO(rnp): add some ID for the specific view here */
- LABEL_GL_OBJECT(GL_FRAMEBUFFER, view->rendered_view.id, s8("Frame View"));
- LABEL_GL_OBJECT(GL_TEXTURE, view->rendered_view.texture.id, s8("Frame View Texture"));
- GenTextureMipmaps(&view->rendered_view.texture);
+ Variable *new_region = add_ui_split(ui, region, &ui->arena, s8(""), 0.5, direction, ui->small_font);
- //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_ANISOTROPIC_8X);
- //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_TRILINEAR);
- //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_BILINEAR);
-
- /* NOTE(rnp): work around raylib's janky texture sampling */
- i32 id = view->rendered_view.texture.id;
- glTextureParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
- glTextureParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
- glTextureParameterfv(id, GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1});
+ if (view == region->u.region_split.left) {
+ region->u.region_split.left = new_region;
+ } else {
+ region->u.region_split.right = new_region;
+ }
+ view->parent = new_region;
+ new_region->u.region_split.left = view;
+ new_region->u.region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, FVT_COPY, 1);
+
+ BeamformerFrameView *bv = new_region->u.region_split.right->u.group.first->u.generic;
+ bv->lateral_scale_bar.min_value = &bv->min_coordinate.x;
+ bv->lateral_scale_bar.max_value = &bv->max_coordinate.x;
+ bv->axial_scale_bar.min_value = &bv->min_coordinate.z;
+ bv->axial_scale_bar.max_value = &bv->max_coordinate.z;
+
+ bv->ctx = old->ctx;
+ bv->needs_update = 1;
+ bv->threshold.u.f32 = old->threshold.u.f32;
+ bv->dynamic_range.u.f32 = old->dynamic_range.u.f32;
+ bv->min_coordinate = old->frame->min_coordinate;
+ bv->max_coordinate = old->frame->max_coordinate;
+
+ bv->frame = SLLPop(ui->frame_freelist);
+ if (!bv->frame) bv->frame = push_struct(&ui->arena, typeof(*bv->frame));
+
+ ASSERT(old->frame->in_flight == 0);
+ mem_copy(bv->frame, old->frame, sizeof(*bv->frame));
+ bv->frame->texture = 0;
+ bv->frame->next = 0;
+ alloc_beamform_frame(0, bv->frame, 0, old->frame->dim, s8("Frame Copy: "), ui->arena);
+ bv->frame->ready_to_present = 1;
+
+ glCopyImageSubData(old->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0,
+ bv->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0,
+ bv->frame->dim.x, bv->frame->dim.y, bv->frame->dim.z);
+ glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
+ /* TODO(rnp): x vs y here */
+ resize_frame_view(bv, (uv2){.x = bv->frame->dim.x, .y = bv->frame->dim.z});
}
+static UI_BUTTON_FN(ui_copy_frame_horizontal) { ui_copy_frame_base(ui, var, RSD_HORIZONTAL); }
+static UI_BUTTON_FN(ui_copy_frame_vertical) { ui_copy_frame_base(ui, var, RSD_VERTICAL); }
+
static b32
view_update(BeamformerUI *ui, BeamformerFrameView *view)
{
@@ -648,9 +771,7 @@ update_frame_views(BeamformerUI *ui)
DrawTexture(view->rendered_view.texture, 0, 0, WHITE);
EndShaderMode();
EndTextureMode();
- SetTraceLogLevel(LOG_NONE);
- GenTextureMipmaps(&view->rendered_view.texture);
- SetTraceLogLevel(LOG_INFO);
+ glGenerateTextureMipmap(view->rendered_view.texture.id);
view->needs_update = 0;
}
}
@@ -726,12 +847,15 @@ push_custom_view_title(Stream *s, Variable *var)
stream_append_byte(s, '%');
} break;
case VT_BEAMFORMER_FRAME_VIEW: {
+ BeamformerFrameView *bv = var->u.generic;
stream_append_s8(s, s8("Frame View"));
- switch (((BeamformerFrameView *)var->u.generic)->type) {
- case FVT_LATEST: stream_append_s8(s, s8(": Live")); break;
- case FVT_COPY: stream_append_s8(s, s8(": Copy")); break;
- case FVT_INDEXED: break;
+ switch (bv->type) {
+ case FVT_LATEST: stream_append_s8(s, s8(": Live [")); break;
+ case FVT_COPY: stream_append_s8(s, s8(": Copy [")); break;
+ case FVT_INDEXED: stream_append_s8(s, s8(": [")); break;
}
+ stream_append_hex_u64(s, bv->frame? bv->frame->id : 0);
+ stream_append_byte(s, ']');
} break;
default: INVALID_CODE_PATH;
}
@@ -898,8 +1022,8 @@ draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mous
if (hover_rect(mouse, close, &view->close->hover_t))
ui->interaction.hot = view->close;
- Color colour = colour_from_normalized(lerp_v4(FOCUSED_COLOUR, FG_COLOUR, view->close->hover_t));
- close = shrink_rect_centered(close, (v2){.x = 18, .y = 18});
+ Color colour = colour_from_normalized(lerp_v4(MENU_CLOSE_COLOUR, FG_COLOUR, view->close->hover_t));
+ close = shrink_rect_centered(close, (v2){.x = 16, .y = 16});
DrawLineEx(close.pos.rl, add_v2(close.pos, close.size).rl, 4, colour);
DrawLineEx(add_v2(close.pos, (v2){.x = close.size.w}).rl,
add_v2(close.pos, (v2){.y = close.size.h}).rl, 4, colour);
@@ -908,11 +1032,13 @@ draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mous
if (view->menu) {
Rect menu;
cut_rect_horizontal(title_rect, title_rect.size.w - title_rect.size.h, &title_rect, &menu);
- if (hover_rect(mouse, menu, &view->menu->hover_t))
- ui->interaction.hot = view->menu;
+ if (hover_rect(mouse, menu, &view->menu->hover_t)) {
+ ui->interaction.hot = view->menu;
+ ui->menu_state.hot_font = &ui->small_font;
+ }
- Color colour = colour_from_normalized(lerp_v4(FOCUSED_COLOUR, FG_COLOUR, view->close->hover_t));
- menu = shrink_rect_centered(menu, (v2){.x = 18, .y = 18});
+ Color colour = colour_from_normalized(lerp_v4(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t));
+ menu = shrink_rect_centered(menu, (v2){.x = 14, .y = 14});
DrawLineEx(add_v2(menu.pos, (v2){.x = menu.size.w / 2}).rl,
add_v2(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h}).rl, 4, colour);
DrawLineEx(add_v2(menu.pos, (v2){.y = menu.size.h / 2}).rl,
@@ -1074,15 +1200,16 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
vr.size.h = MAX(0, display_rect.size.h - pad);
vr.size.w = MAX(0, display_rect.size.w - pad);
+ /* TODO(rnp): ideally we hook up both versions to view->min/max */
+ v4 min = (view->type == FVT_LATEST)? bp->output_min_coordinate : view->min_coordinate;
+ v4 max = (view->type == FVT_LATEST)? bp->output_max_coordinate : view->max_coordinate;
+
/* TODO(rnp): make this depend on the requested draw orientation (x-z or y-z or x-y) */
v2 output_dim = {
.x = frame->max_coordinate.x - frame->min_coordinate.x,
.y = frame->max_coordinate.z - frame->min_coordinate.z,
};
- v2 requested_dim = {
- .x = bp->output_max_coordinate.x - bp->output_min_coordinate.x,
- .y = bp->output_max_coordinate.z - bp->output_min_coordinate.z,
- };
+ v2 requested_dim = {.x = max.x - min.x, .y = max.z - min.z};
f32 aspect = requested_dim.h / requested_dim.w;
if (display_rect.size.h < (vr.size.w * aspect) + pad) {
@@ -1103,7 +1230,7 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
/* TODO(rnp): this also depends on x-y, y-z, x-z */
v2 texture_start = {
.x = pixels_per_meter.x * 0.5 * (output_dim.x - requested_dim.x),
- .y = pixels_per_meter.y * (frame->max_coordinate.z - bp->output_max_coordinate.z),
+ .y = pixels_per_meter.y * (frame->max_coordinate.z - max.z),
};
Rectangle tex_r = {texture_start.x, texture_start.y, texture_points.x, -texture_points.y};
@@ -1118,8 +1245,8 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
Stream buf = arena_stream(&tmp);
do_scale_bar(ui, &buf, &view->lateral_scale_bar, SB_LATERAL, mouse,
(Rect){.pos = start_pos, .size = vr.size},
- bp->output_min_coordinate.x * 1e3,
- bp->output_max_coordinate.x * 1e3, s8(" mm"));
+ *view->lateral_scale_bar.min_value * 1e3,
+ *view->lateral_scale_bar.max_value * 1e3, s8(" mm"));
}
start_pos = vr.pos;
@@ -1130,8 +1257,8 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
Stream buf = arena_stream(&tmp);
do_scale_bar(ui, &buf, &view->axial_scale_bar, SB_AXIAL, mouse,
(Rect){.pos = start_pos, .size = vr.size},
- bp->output_max_coordinate.z * 1e3,
- bp->output_min_coordinate.z * 1e3, s8(" mm"));
+ *view->axial_scale_bar.max_value * 1e3,
+ *view->axial_scale_bar.min_value * 1e3, s8(" mm"));
}
v2 pixels_to_mm = output_dim;
@@ -1146,8 +1273,8 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
v2 relative_mouse = sub_v2(mouse, vr.pos);
v2 mm = mul_v2(relative_mouse, pixels_to_mm);
- mm.x += 1e3 * bp->output_min_coordinate.x;
- mm.y += 1e3 * bp->output_min_coordinate.z;
+ mm.x += 1e3 * min.x;
+ mm.y += 1e3 * min.z;
Arena tmp = a;
Stream buf = arena_stream(&tmp);
@@ -1494,6 +1621,52 @@ draw_active_text_box(BeamformerUI *ui, Variable *var)
}
static void
+draw_active_menu(BeamformerUI *ui, Arena arena, Variable *menu, v2 mouse, Rect window)
+{
+ ASSERT(menu->type == VT_GROUP);
+ MenuState *ms = &ui->menu_state;
+
+ f32 max_label_width = 0;
+
+ Variable *item = menu->u.group.first;
+ u32 item_count = 0;
+ while (item) {
+ max_label_width = MAX(max_label_width, item->name_width);
+ item = item->next;
+ item_count++;
+ }
+
+ v2 at = ms->at;
+ f32 menu_width = max_label_width + 8;
+ f32 menu_height = item_count * ms->font->baseSize + (item_count) * 2;
+
+ if (at.x + menu_width > window.size.w)
+ at.x = window.size.w - menu_width - 16;
+ if (at.y + menu_height > window.size.h)
+ at.y = window.size.h - menu_height - 12;
+ /* TODO(rnp): scroll menu if it doesn't fit on screen */
+
+ Rect menu_rect = {.pos = at, .size = {.w = menu_width, .h = menu_height}};
+ Rect bg_rect = extend_rect_centered(menu_rect, (v2){.x = 12, .y = 8});
+ menu_rect = extend_rect_centered(menu_rect, (v2){.x = 6, .y = 4});
+ DrawRectangleRounded(bg_rect.rl, 0.1, 0, fade(BLACK, 0.8));
+ DrawRectangleRounded(menu_rect.rl, 0.1, 0, colour_from_normalized(BG_COLOUR));
+
+ /* TODO(rnp): last element has too much vertical space */
+ item = menu->u.group.first;
+ while (item) {
+ at.y += draw_beamformer_variable(ui, arena, item, at, mouse, &item->hover_t,
+ menu_width, 0, FG_COLOUR, &ui->small_font).y;
+ item = item->next;
+ if (item) {
+ DrawLineEx((v2){.x = at.x - 3, .y = at.y}.rl,
+ add_v2(at, (v2){.w = menu_width + 3}).rl, 2, fade(BLACK, 0.8));
+ at.y += 2;
+ }
+ }
+}
+
+static void
draw_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse)
{
if (var->type != VT_UI_REGION_SPLIT) {
@@ -1651,6 +1824,13 @@ scroll_interaction(Variable *var, f32 delta)
}
static void
+begin_menu_input(MenuState *ms, v2 mouse)
+{
+ ms->at = mouse;
+ ms->font = ms->hot_font;
+}
+
+static void
begin_text_input(InputState *is, Variable *var, v2 mouse)
{
Stream s = {.cap = ARRAY_COUNT(is->buf), .data = is->buf};
@@ -1815,14 +1995,16 @@ scale_bar_interaction(BeamformerCtx *ctx, v2 mouse)
*sb->max_value = MIN(max, sb->limits.y);
sb->zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY};
- ui->flush_params = 1;
+ if (sb->causes_compute)
+ ui->flush_params = 1;
}
}
if (mouse_right_pressed) {
v2_sll *savepoint = sb->savepoint_stack;
if (savepoint) {
- ui->flush_params = 1;
+ if (sb->causes_compute)
+ ui->flush_params = 1;
*sb->min_value = savepoint->v.x;
*sb->max_value = savepoint->v.y;
sb->savepoint_stack = SLLPush(savepoint, ui->scale_bar_savepoint_freelist);
@@ -1835,7 +2017,8 @@ scale_bar_interaction(BeamformerCtx *ctx, v2 mouse)
*sb->max_value += mouse_wheel * sb->scroll_scale.y;
*sb->min_value = MAX(sb->limits.x, *sb->min_value);
*sb->max_value = MIN(sb->limits.y, *sb->max_value);
- ui->flush_params = 1;
+ if (sb->causes_compute)
+ ui->flush_params = 1;
}
}
@@ -1849,7 +2032,14 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mous
switch (is->hot->type) {
case VT_NULL: is->type = IT_NOP; break;
case VT_B32: is->type = IT_SET; break;
- case VT_GROUP: is->type = IT_SET; break;
+ case VT_GROUP: {
+ if (mouse_left_pressed && is->hot->flags & V_MENU) {
+ is->type = IT_MENU;
+ begin_menu_input(&ui->menu_state, input->mouse);
+ } else {
+ is->type = IT_SET;
+ }
+ } break;
case VT_UI_REGION_SPLIT: is->type = IT_DRAG; break;
case VT_UI_VIEW: {
if (scroll) is->type = IT_SCROLL;
@@ -1871,9 +2061,13 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mous
} else {
parent->u.region_split.right = remaining;
}
+ remaining->parent = parent;
SLLPush(region, ui->variable_freelist);
} break;
+ case VT_UI_BUTTON: {
+ is->hot->u.button(ui, is->hot);
+ } break;
case VT_BEAMFORMER_VARIABLE: {
if (is->hot->u.beamformer_variable.store_type == VT_B32) {
is->type = IT_SET;
@@ -1926,6 +2120,7 @@ ui_end_interact(BeamformerCtx *ctx, v2 mouse)
case IT_DISPLAY: display_interaction_end(ui); break;
case IT_SCROLL: scroll_interaction(is->active, GetMouseWheelMoveV().y); break;
case IT_TEXT: end_text_input(&ui->text_input_state, is->active); break;
+ case IT_MENU: break;
case IT_SCALE_BAR: break;
case IT_DRAG: break;
default: INVALID_CODE_PATH;
@@ -1963,10 +2158,11 @@ ui_interact(BeamformerCtx *ctx, BeamformerInput *input)
switch (is->type) {
case IT_NONE: break;
case IT_NOP: break;
+ case IT_MENU: break;
case IT_DISPLAY: display_interaction(ui, input->mouse, GetMouseWheelMoveV().y); break;
- case IT_SCROLL: ui_end_interact(ctx, input->mouse); break;
- case IT_SET: ui_end_interact(ctx, input->mouse); break;
- case IT_TEXT: update_text_input(&ui->text_input_state, is->active); break;
+ case IT_SCROLL: ui_end_interact(ctx, input->mouse); break;
+ case IT_SET: ui_end_interact(ctx, input->mouse); break;
+ case IT_TEXT: update_text_input(&ui->text_input_state, is->active); break;
case IT_DRAG: {
if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) {
ui_end_interact(ctx, input->mouse);
@@ -2031,6 +2227,7 @@ ui_init(BeamformerCtx *ctx, Arena store)
}
ui = ctx->ui = push_struct(&store, typeof(*ui));
+ ui->os = &ctx->os;
ui->arena = store;
/* TODO: build these into the binary */
@@ -2043,24 +2240,15 @@ ui_init(BeamformerCtx *ctx, Arena store)
RSD_HORIZONTAL, ui->font);
split->u.region_split.left = add_ui_split(ui, split, &ui->arena, s8(""), 0.475,
RSD_VERTICAL, ui->font);
- split->u.region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, FVT_LATEST);
+ split->u.region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, FVT_LATEST, 0);
BeamformerFrameView *bv = split->u.region_split.right->u.group.first->u.generic;
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;
bv->axial_scale_bar.max_value = &ui->params.output_max_coordinate.z;
-
- bv->lateral_scale_bar.limits = (v2){.x = -1, .y = 1};
- bv->axial_scale_bar.limits = (v2){.x = 0, .y = 1};
-
- bv->lateral_scale_bar.scroll_scale = (v2){.x = -0.5e-3, .y = 0.5e-3};
- bv->axial_scale_bar.scroll_scale = (v2){.x = 0, .y = 1e-3};
-
- 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->axial_scale_bar.causes_compute = 1;
+ bv->lateral_scale_bar.causes_compute = 1;
bv->ctx = &ctx->fsctx;
split = split->u.region_split.left;
@@ -2131,5 +2319,7 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw
draw_ui_regions(ui, window_rect, mouse);
if (ui->interaction.type == IT_TEXT)
draw_active_text_box(ui, ui->interaction.active);
+ if (ui->interaction.type == IT_MENU)
+ draw_active_menu(ui, ui->arena, ui->interaction.active, mouse, window_rect);
EndDrawing();
}
diff --git a/util.c b/util.c
@@ -245,6 +245,24 @@ stream_append_u64(Stream *s, u64 n)
}
static void
+stream_append_hex_u64(Stream *s, u64 n)
+{
+ if (!s->errors) {
+ static u8 hex[16] = {"0123456789abcdef"};
+ u8 buf[16];
+ u8 *end = buf + sizeof(buf);
+ u8 *beg = end;
+ while (n) {
+ *--beg = hex[n & 0x0F];
+ n >>= 4;
+ }
+ while (end - beg < 2)
+ *--beg = '0';
+ stream_append(s, beg, end - beg);
+ }
+}
+
+static void
stream_append_i64(Stream *s, i64 n)
{
if (n < 0) {
diff --git a/util.h b/util.h
@@ -60,6 +60,17 @@
/* NOTE(rnp): evaluates to the old value of v->next */
#define SLLPush(v, list) (v)->next; (v)->next = (list), (list) = v
+#define DLLPushDown(v, list) do { \
+ (v)->next = (list); \
+ if ((v)->next) (v)->next->prev = (v); \
+ (list) = (v); \
+} while (0)
+
+#define DLLRemove(v) do { \
+ if ((v)->next) (v)->next->prev = (v)->prev; \
+ if ((v)->prev) (v)->prev->next = (v)->next; \
+} while (0)
+
#define KB(a) ((a) << 10ULL)
#define MB(a) ((a) << 20ULL)
#define GB(a) ((a) << 30ULL)