Commit: 0ca3fddb96126960f05807675b2da3d7a7fdf1ba
Parent: 752f76c0ff806292854b956a21ac5ab9260b2a8f
Author: Randy Palamar
Date: Sun, 13 Apr 2025 12:54:12 -0600
ui: add power scale for rendered views and make it default
closes: #32
Diffstat:
4 files changed, 104 insertions(+), 53 deletions(-)
diff --git a/beamformer.h b/beamformer.h
@@ -56,8 +56,11 @@ typedef struct {
typedef struct {
Shader shader;
b32 updated;
+ /* TODO(rnp): cleanup! */
i32 db_cutoff_id;
i32 threshold_id;
+ i32 gamma_id;
+ i32 log_scale_id;
} FragmentShaderCtx;
#include "beamformer_parameters.h"
diff --git a/shaders/render.glsl b/shaders/render.glsl
@@ -5,8 +5,10 @@ in vec2 fragTexCoord;
out vec4 v_out_colour;
layout(binding = 0) uniform sampler3D u_out_data_tex;
-layout(location = 1) uniform float u_db_cutoff = -60;
-layout(location = 2) uniform float u_threshold = 40;
+layout(location = 1) uniform float u_db_cutoff = 60;
+layout(location = 2) uniform float u_threshold = 40;
+layout(location = 3) uniform float u_gamma = 1;
+layout(location = 4) uniform bool u_log_scale;
/* input: h [0,360] | s,v [0, 1] *
* output: rgb [0,1] */
@@ -19,24 +21,26 @@ vec3 hsv2rgb(vec3 hsv)
void main()
{
- ivec3 out_data_dim = textureSize(u_out_data_tex, 0);
+ ivec2 out_data_dim = textureSize(u_out_data_tex, 0).xz;
- /* TODO: select between x and y with potentially a slice if viewing rendered volumes */
- ivec2 coord = ivec2(fragTexCoord * vec2(out_data_dim.xz));
- vec2 min_max = texelFetch(u_out_data_tex, ivec3(0), textureQueryLevels(u_out_data_tex) - 1).xy;
+ //vec2 min_max = texelFetch(u_out_data_tex, ivec3(0), textureQueryLevels(u_out_data_tex) - 1).xy;
+ /* TODO(rnp): select between x and y and specify slice */
+ ivec2 coord = ivec2(fragTexCoord * vec2(out_data_dim));
ivec3 smp_coord = ivec3(coord.x, 0, coord.y);
float smp = length(texelFetch(u_out_data_tex, smp_coord, 0).xy);
- //float absmax = max(abs(min_max.y), abs(min_max.x));
- //smp = 20 * log(smp / absmax) / log(10);
-
float threshold_val = pow(10.0f, u_threshold / 20.0f);
smp = clamp(smp, 0.0f, threshold_val);
- smp = 20 * log(smp / threshold_val) / log(10);
-
- smp = clamp(smp, u_db_cutoff, 0) / u_db_cutoff;
- smp = 1 - smp;
+ smp = smp / threshold_val;
+
+ if (u_log_scale) {
+ smp = 20 * log(smp) / log(10);
+ smp = clamp(smp, -u_db_cutoff, 0) / -u_db_cutoff;
+ smp = 1 - smp;
+ } else {
+ smp = pow(smp, u_gamma);
+ }
//v_out_colour = vec4(hsv2rgb(vec3(360 * smp, 0.8, 0.95)), 1);
v_out_colour = vec4(smp, smp, smp, 1);
diff --git a/static.c b/static.c
@@ -195,6 +195,8 @@ static FILE_WATCH_CALLBACK_FN(reload_render_shader)
ctx->shader = updated_fs;
ctx->db_cutoff_id = GetShaderLocation(updated_fs, "u_db_cutoff");
ctx->threshold_id = GetShaderLocation(updated_fs, "u_threshold");
+ ctx->gamma_id = GetShaderLocation(updated_fs, "u_gamma");
+ ctx->log_scale_id = GetShaderLocation(updated_fs, "u_log_scale");
ctx->updated = 1;
}
diff --git a/ui.c b/ui.c
@@ -28,7 +28,6 @@
* [ ]: show full non-truncated string on hover
* [ ]: refactor: hovered element type and show hovered element in full even when truncated
* [ ]: visual indicator for broken shader stage gh#27
- * [ ]: power/log scale render toggle gh#32
* [ ]: V_UP_HIERARCHY, V_DOWN_HIERARCHY - set active interaction to parent or child ?
* [ ]: bug: cross-plane view with different dimensions for each plane
*/
@@ -98,6 +97,8 @@ typedef struct v2_sll {
v2 v;
} v2_sll;
+typedef struct { f32 val, scale; } scaled_f32;
+
typedef struct BeamformerUI BeamformerUI;
typedef struct Variable Variable;
@@ -134,6 +135,7 @@ typedef enum {
VT_U32,
VT_GROUP,
VT_CYCLER,
+ VT_SCALED_F32,
VT_BEAMFORMER_VARIABLE,
VT_BEAMFORMER_FRAME_VIEW,
VT_COMPUTE_STATS_VIEW,
@@ -235,6 +237,7 @@ struct Variable {
UIView view;
VariableCycler cycler;
VariableGroup group;
+ scaled_f32 scaled_f32;
b32 b32;
i32 i32;
u32 u32;
@@ -275,18 +278,22 @@ typedef enum {
typedef struct BeamformerFrameView {
ScaleBar lateral_scale_bar;
ScaleBar axial_scale_bar;
+
+ /* NOTE(rnp): these are pointers because they are added to the menu and will
+ * be put onto the freelist if the view is closed */
Variable *lateral_scale_bar_active;
Variable *axial_scale_bar_active;
+ Variable *log_scale;
+ /* NOTE(rnp): if type is LATEST selects which type of latest to use
+ * if type is INDEXED selects the index */
+ Variable *cycler;
v4 min_coordinate;
v4 max_coordinate;
- /* NOTE(rnp): if type is LATEST selects which type of latest to use
- * if typs is INDEXED selects the index */
- Variable *cycler;
-
Variable threshold;
Variable dynamic_range;
+ Variable gamma;
FragmentShaderCtx *ctx;
BeamformFrame *frame;
@@ -416,7 +423,9 @@ 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_F32: stream_append_f64(s, var->u.f32, 100); break;
case VT_B32: stream_append_s8(s, var->u.b32 ? s8("True") : s8("False")); break;
+ case VT_SCALED_F32: stream_append_f64(s, var->u.scaled_f32.val, 100); break;
case VT_BEAMFORMER_VARIABLE: {
BeamformerVariable *bv = &var->u.beamformer_variable;
switch (bv->store_type) {
@@ -434,10 +443,6 @@ stream_append_variable(Stream *s, Variable *var)
if (var->u.cycler.labels) stream_append_s8(s, var->u.cycler.labels[index]);
else stream_append_u64(s, index);
} break;
- case VT_F32: {
- f32 scale = 1;
- stream_append_variable_base(s, VT_F32, &var->u.f32, &scale);
- } break;
default: INVALID_CODE_PATH;
}
}
@@ -728,15 +733,19 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena,
DLLPushDown(bv, ui->views);
var->u.generic = bv;
+ bv->type = type;
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);
+ fill_variable(&bv->gamma, var, s8("Gamma:"), V_INPUT|V_TEXT|V_UPDATE_VIEW,
+ VT_SCALED_F32, ui->small_font);
- bv->type = type;
- bv->dynamic_range.u.f32 = -50.0f;
- bv->threshold.u.f32 = 40.0f;
+ bv->dynamic_range.u.f32 = 50.0f;
+ bv->threshold.u.f32 = 55.0f;
+ bv->gamma.u.scaled_f32.val = 1.0f;
+ bv->gamma.u.scaled_f32.scale = 0.05f;
bv->lateral_scale_bar.limits = (v2){.x = -1, .y = 1};
bv->axial_scale_bar.limits = (v2){.x = 0, .y = 1};
@@ -771,6 +780,9 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena,
default: break;
}
+ bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"),
+ V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32,
+ ui->small_font);
bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"),
V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font);
bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"),
@@ -927,8 +939,10 @@ update_frame_views(BeamformerUI *ui)
BeginShaderMode(view->ctx->shader);
glUseProgram(view->ctx->shader.id);
glBindTextureUnit(0, frame->texture);
- glUniform1f(view->ctx->db_cutoff_id, view->dynamic_range.u.f32);
- glUniform1f(view->ctx->threshold_id, view->threshold.u.f32);
+ glUniform1f(view->ctx->db_cutoff_id, view->dynamic_range.u.f32);
+ glUniform1f(view->ctx->threshold_id, view->threshold.u.f32);
+ glUniform1f(view->ctx->gamma_id, view->gamma.u.scaled_f32.val);
+ glUniform1ui(view->ctx->log_scale_id, view->log_scale->u.b32);
DrawTexture(view->rendered_view.texture, 0, 0, WHITE);
EndShaderMode();
EndTextureMode();
@@ -1161,10 +1175,13 @@ hover_rect(v2 mouse, Rect rect, f32 *hover_t)
static b32
hover_var(BeamformerUI *ui, v2 mouse, Rect rect, Variable *var)
{
- b32 result = hover_rect(mouse, rect, &var->hover_t);
- if (result) {
- ui->interaction.hot_type = IT_NONE;
- ui->interaction.hot = var;
+ b32 result = 0;
+ if (ui->interaction.type != IT_DRAG || ui->interaction.active == var) {
+ result = hover_rect(mouse, rect, &var->hover_t);
+ if (result) {
+ ui->interaction.hot_type = IT_NONE;
+ ui->interaction.hot = var;
+ }
}
return result;
}
@@ -1485,8 +1502,8 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
b32 drew_coordinates = 0;
f32 remaining_width = vr.size.w;
if (CheckCollisionPointRec(mouse.rl, vr.rl)) {
- is->hot_type = IT_DISPLAY;
- is->hot = var;
+ is->hot_type = IT_DISPLAY;
+ is->hot = var;
v2 relative_mouse = sub_v2(mouse, vr.pos);
v2 mm = mul_v2(relative_mouse, pixels_to_mm);
@@ -1552,25 +1569,32 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
draw_text(stream_to_s8(&buf), txt_p, &text_spec);
}
+ /* TODO(rnp): cleanup this whole mess with a table */
if (remaining_width > view->dynamic_range.name_width || !drew_coordinates) {
f32 max_prefix_width = MAX(view->threshold.name_width, view->dynamic_range.name_width);
+ b32 log_scale = view->log_scale->u.b32;
+ f32 items = log_scale ? 2 : 3;
v2 end = add_v2(vr.pos, vr.size);
- f32 start_y = MAX(end.y - 4 - 2 * text_spec.font->baseSize, vr.pos.y);
+ f32 start_y = MAX(end.y - 4 - items * text_spec.font->baseSize, vr.pos.y);
end.y -= text_spec.font->baseSize;
v2 at = {.x = vr.pos.x + 4, .y = start_y};
- at.y += draw_text(view->dynamic_range.name, at, &text_spec).y;
+ if (!log_scale && at.y < end.y) at.y += draw_text(view->gamma.name, at, &text_spec).y;
if (at.y < end.y) at.y += draw_text(view->threshold.name, at, &text_spec).y;
+ if (at.y < end.y) at.y += draw_text(view->dynamic_range.name, at, &text_spec).y;
at.y = start_y;
at.x += max_prefix_width + 8;
text_spec.limits.size.x = end.x - at.x;
- v2 size = draw_variable(ui, a, &view->dynamic_range, at, mouse, RULER_COLOUR, text_spec);
-
- f32 max_center_width = size.w;
- at.y += size.h;
+ v2 size;
+ f32 max_center_width = 0;
+ if (!log_scale && at.y < end.y) {
+ size = draw_variable(ui, a, &view->gamma, at, mouse, RULER_COLOUR, text_spec);
+ max_center_width = MAX(size.w, max_center_width);
+ at.y += size.h;
+ }
if (at.y < end.y) {
size = draw_variable(ui, a, &view->threshold, at, mouse, RULER_COLOUR, text_spec);
@@ -1578,10 +1602,15 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa
at.y += size.h;
}
- at.y = start_y;
- at.x += max_center_width + 8;
- text_spec.limits.size.x = end.x - at.x;
- draw_text(s8(" [dB]"), at, &text_spec);
+ if (at.y < end.y) {
+ size = draw_variable(ui, a, &view->dynamic_range, at, mouse, RULER_COLOUR, text_spec);
+ max_center_width = MAX(size.w, max_center_width);
+ at.x += max_center_width + 8;
+ text_spec.limits.size.x = end.x - at.x;
+ draw_text(s8(" [dB]"), at, &text_spec);
+ at.x -= max_center_width + 8;
+ at.y += size.h;
+ }
}
}
@@ -1794,7 +1823,7 @@ draw_active_text_box(BeamformerUI *ui, Variable *var)
v2 text_size = measure_text(*is->font, text);
v2 text_position = {.x = box.pos.x, .y = box.pos.y + (box.size.h - text_size.h) / 2};
- f32 cursor_width = (is->cursor == is->buf_len) ? 16 : 4;
+ f32 cursor_width = (is->cursor == is->buf_len) ? 0.55 * is->font->baseSize : 4;
f32 cursor_offset = measure_text(*is->font, (s8){.data = text.data, .len = is->cursor}).w;
cursor_offset += text_position.x;
@@ -1997,6 +2026,10 @@ static void
ui_store_variable(Variable *var, void *new_value)
{
switch (var->type) {
+ case VT_SCALED_F32: {
+ v2 limits = {.x = -F32_INFINITY, .y = F32_INFINITY};
+ ui_store_variable_base(VT_F32, &var->u.scaled_f32.val, new_value, &limits);
+ } break;
case VT_F32: {
v2 limits = {.x = -F32_INFINITY, .y = F32_INFINITY};
ui_store_variable_base(VT_F32, &var->u.f32, new_value, &limits);
@@ -2025,6 +2058,7 @@ static void
scroll_interaction(Variable *var, f32 delta)
{
switch (var->type) {
+ case VT_SCALED_F32: var->u.scaled_f32.val += delta * var->u.scaled_f32.scale; break;
case VT_BEAMFORMER_VARIABLE: {
BeamformerVariable *bv = &var->u.beamformer_variable;
scroll_interaction_base(bv->store_type, bv->store, delta * bv->params.scroll_scale);
@@ -2157,7 +2191,11 @@ display_interaction(BeamformerUI *ui, v2 mouse, f32 scroll)
if (scroll) {
ASSERT(ui->interaction.active->type == VT_BEAMFORMER_FRAME_VIEW);
BeamformerFrameView *bv = ui->interaction.active->u.generic;
- bv->threshold.u.f32 += scroll;
+ if (bv->log_scale->u.b32) {
+ bv->threshold.u.f32 += scroll;
+ } else {
+ bv->gamma.u.scaled_f32.val += scroll * bv->gamma.u.scaled_f32.scale;
+ }
bv->needs_update = 1;
}
@@ -2307,6 +2345,7 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mous
break;
}
} /* FALLTHROUGH */
+ case VT_SCALED_F32:
case VT_F32: {
if (scroll) {
is->type = IT_SCROLL;
@@ -2366,9 +2405,17 @@ ui_end_interact(BeamformerCtx *ctx, v2 mouse)
if (is->active->flags & V_CAUSES_COMPUTE)
ui->flush_params = 1;
if (is->active->flags & V_UPDATE_VIEW) {
- Variable *frame_view = is->active->parent;
- ASSERT(frame_view && frame_view->type == VT_BEAMFORMER_FRAME_VIEW);
- ((BeamformerFrameView *)frame_view->u.generic)->needs_update = 1;
+ Variable *parent = is->active->parent;
+ BeamformerFrameView *frame;
+ /* TODO(rnp): more straight forward way of achieving this */
+ if (parent->type == VT_BEAMFORMER_FRAME_VIEW) {
+ frame = parent->u.generic;
+ } else {
+ ASSERT(parent->flags & V_MENU);
+ ASSERT(parent->parent->u.group.first->type == VT_BEAMFORMER_FRAME_VIEW);
+ frame = parent->parent->u.group.first->u.generic;
+ }
+ frame->needs_update = 1;
}
if (menu_child && (is->active->flags & V_CLOSES_MENU) == 0) {
@@ -2431,11 +2478,6 @@ ui_interact(BeamformerCtx *ctx, BeamformerInput *input)
} break;
default: break;
}
-
- if (is->active != is->hot) {
- is->active->hover_t += HOVER_SPEED * dt_for_frame;
- is->active->hover_t = CLAMP01(is->active->hover_t);
- }
}
} break;
case IT_SCALE_BAR: scale_bar_interaction(ctx, input->mouse); break;