ogl_beamforming

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

Commit: d93ee2a1092dceaf552f960c356c145e36b2f8c2
Parent: 61f780dc2b0d0c865bfc66cbf6217d4ac0a7da15
Author: Randy Palamar
Date:   Fri,  6 Sep 2024 17:09:27 -0600

fix the coordinate system

What we refer to as Z in experiments now corresponds to Z in the
output image. This took way longer than it needed to due to OpenGL
thinking that someone would want to bind a single slice of
TEXTURE_3D (TEXTURE_2D_ARRAY exists for that purpose).

Diffstat:
Mbeamformer.c | 18++++++++++--------
Mbeamformer_parameters.h | 6+++---
Mshaders/2d_hercules.glsl | 48+++++++++++++++++++++++++-----------------------
Mshaders/demod.glsl | 5++---
Mshaders/hadamard.glsl | 5++---
Mshaders/min_max.glsl | 8++++----
Mshaders/render.glsl | 11+++++++----
Mshaders/uforces.glsl | 32++++++++++++++------------------
Mui.c | 31+++++++++++++++----------------
Mutil.c | 7+++++++
10 files changed, 89 insertions(+), 82 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -6,9 +6,10 @@ static void alloc_output_image(BeamformerCtx *ctx) { BeamformerParameters *bp = &ctx->params->raw; - ctx->out_data_dim.x = ORONE(bp->output_points.x); - ctx->out_data_dim.y = ORONE(bp->output_points.y); - ctx->out_data_dim.z = ORONE(bp->output_points.z); + ctx->out_data_dim.x = round_down_power_of_2(ORONE(bp->output_points.x)); + ctx->out_data_dim.y = round_down_power_of_2(ORONE(bp->output_points.y)); + ctx->out_data_dim.z = round_down_power_of_2(ORONE(bp->output_points.z)); + bp->output_points = ctx->out_data_dim; /* NOTE: allocate storage for beamformed output data; * this is shared between compute and fragment shaders */ @@ -24,7 +25,8 @@ alloc_output_image(BeamformerCtx *ctx) glTexStorage3D(GL_TEXTURE_3D, ctx->out_texture_mips, GL_RG32F, odim.x, odim.y, odim.z); UnloadRenderTexture(ctx->fsctx.output); - ctx->fsctx.output = LoadRenderTexture(odim.x, odim.y); + /* TODO: select odim.x vs odim.y */ + ctx->fsctx.output = LoadRenderTexture(odim.x, odim.z); GenTextureMipmaps(&ctx->fsctx.output.texture); //SetTextureFilter(ctx->fsctx.output.texture, TEXTURE_FILTER_ANISOTROPIC_8X); //SetTextureFilter(ctx->fsctx.output.texture, TEXTURE_FILTER_TRILINEAR); @@ -158,7 +160,7 @@ do_compute_shader(BeamformerCtx *ctx, enum compute_shaders shader) u32 width = ctx->out_data_dim.x >> i; u32 height = ctx->out_data_dim.y >> i; u32 depth = ctx->out_data_dim.z >> i; - glDispatchCompute(ORONE(width / 32), ORONE(height / 32), ORONE(depth)); + glDispatchCompute(ORONE(width / 32), ORONE(height), ORONE(depth / 32)); glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); } break; @@ -166,13 +168,13 @@ do_compute_shader(BeamformerCtx *ctx, enum compute_shaders shader) case CS_UFORCES: glActiveTexture(GL_TEXTURE0 + ctx->out_texture_unit); glBindTexture(GL_TEXTURE_3D, ctx->out_texture); - glBindImageTexture(ctx->out_texture_unit, ctx->out_texture, 0, GL_FALSE, 0, + glBindImageTexture(ctx->out_texture_unit, ctx->out_texture, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RG32F); glUniform1i(csctx->out_data_tex_id, ctx->out_texture_unit); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, csctx->rf_data_ssbos[input_ssbo_idx]); glDispatchCompute(ORONE(ctx->out_data_dim.x / 32), - ORONE(ctx->out_data_dim.y / 32), - ORONE(ctx->out_data_dim.z)); + ctx->out_data_dim.y, + ORONE(ctx->out_data_dim.z / 32)); break; default: ASSERT(0); } diff --git a/beamformer_parameters.h b/beamformer_parameters.h @@ -18,9 +18,9 @@ typedef struct { f32 lpf_coefficients[64]; /* Low Pass Filter Cofficients */ uv4 dec_data_dim; /* Samples * Channels * Acquisitions; last element ignored */ uv4 output_points; /* Width * Height * Depth; last element ignored */ + v4 output_min_coordinate; /* [m] Back-Top-Left corner of output region (w ignored) */ + v4 output_max_coordinate; /* [m] Front-Bottom-Right corner of output region (w ignored)*/ uv2 rf_raw_dim; /* Raw Data Dimensions */ - v2 output_min_xz; /* [m] Top left corner of output region */ - v2 output_max_xz; /* [m] Bottom right corner of output region */ v2 xdc_min_xy; /* [m] Min center of transducer elements */ v2 xdc_max_xy; /* [m] Max center of transducer elements */ u32 channel_offset; /* Offset into channel_mapping: 0 or 128 (rows or columns) */ @@ -31,5 +31,5 @@ typedef struct { f32 focal_depth; /* [m] */ f32 time_offset; /* pulse length correction time [s] */ u32 uforces; /* mode is UFORCES (1) or FORCES (0) */ - f32 off_axis_pos; /* Where on the 3rd axis to render the image (Hercules only)*/ + f32 _pad[2]; } BeamformerParameters; diff --git a/shaders/2d_hercules.glsl b/shaders/2d_hercules.glsl @@ -1,6 +1,6 @@ /* See LICENSE for license details. */ #version 460 core -layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; +layout(local_size_x = 32, local_size_y = 1, local_size_z = 32) in; layout(std430, binding = 1) readonly restrict buffer buffer_1 { vec2 rf_data[]; @@ -12,9 +12,9 @@ layout(std140, binding = 0) uniform parameters { vec4 lpf_coefficients[16]; /* Low Pass Filter Cofficients */ uvec4 dec_data_dim; /* Samples * Channels * Acquisitions; last element ignored */ uvec4 output_points; /* Width * Height * Depth; last element ignored */ + vec4 output_min_coord; /* [m] Top left corner of output region */ + vec4 output_max_coord; /* [m] Bottom right corner of output region */ uvec2 rf_raw_dim; /* Raw Data Dimensions */ - vec2 output_min_xz; /* [m] Top left corner of output region */ - vec2 output_max_xz; /* [m] Bottom right corner of output region */ vec2 xdc_min_xy; /* [m] Min center of transducer elements */ vec2 xdc_max_xy; /* [m] Max center of transducer elements */ uint channel_offset; /* Offset into channel_mapping: 0 or 128 (rows or columns) */ @@ -25,10 +25,9 @@ layout(std140, binding = 0) uniform parameters { float focal_depth; /* [m] */ float time_offset; /* pulse length correction time [s] */ uint uforces; /* mode is UFORCES (1) or FORCES (0) */ - float off_axis_pos; /* Where on the 3rd axis to render the image (Hercules only) */ }; -layout(rg32f, location = 1) uniform image3D u_out_data_tex; +layout(rg32f, location = 1) uniform writeonly image3D u_out_data_tex; #define C_SPLINE 0.5 @@ -66,19 +65,17 @@ vec2 cubic(uint ridx, float x) void main() { - vec2 pixel = vec2(gl_GlobalInvocationID.xy); + vec3 voxel = vec3(gl_GlobalInvocationID.xyz); ivec3 out_coord = ivec3(gl_GlobalInvocationID.xyz); ivec3 out_data_dim = imageSize(u_out_data_tex); /* NOTE: Convert pixel to physical coordinates */ vec2 xdc_size = abs(xdc_max_xy - xdc_min_xy); - vec2 output_size = abs(output_max_xz - output_min_xz); - - /* TODO: for now assume y-dimension is along transducer center */ - vec3 image_point = vec3( - output_min_xz.x + pixel.x * output_size.x / out_data_dim.x, - off_axis_pos, - output_min_xz.y + pixel.y * output_size.y / out_data_dim.y + vec4 output_size = abs(output_max_coord - output_min_coord); + vec3 image_point = vec3( + output_min_coord.x + voxel.x * output_size.x / out_data_dim.x, + output_min_coord.y + voxel.y * output_size.y / out_data_dim.y, + output_min_coord.z + voxel.z * output_size.z / out_data_dim.z ); /* NOTE: used for constant F# dynamic receive apodization. This is implemented as: @@ -88,15 +85,14 @@ void main() * \ |z_e - z_i|/ * * where x,z_e are transducer element positions and x,z_i are image positions. */ - float f_num = output_size.y / output_size.x; + float f_num = output_size.z / output_size.x; float apod_arg = f_num * 0.5 * radians(360) / abs(image_point.z); /* NOTE: for I-Q data phase correction */ float iq_time_scale = (lpf_order > 0)? radians(360) * center_frequency : 0; vec3 starting_dist = vec3(image_point.x - xdc_min_xy.x, image_point.y - xdc_min_xy.y, image_point.z); - float dx = xdc_size.x / float(dec_data_dim.y); - float dy = xdc_size.y / float(dec_data_dim.y); + vec3 delta = vec3(xdc_size.x / float(dec_data_dim.y), xdc_size.y / float(dec_data_dim.y), 0); float dzsign = sign(image_point.z - focal_depth); /* NOTE: offset correcting for both pulse length and low pass filtering */ @@ -104,14 +100,17 @@ void main() vec2 sum = vec2(0); vec3 rdist = starting_dist; - /* NOTE: skip first acquisition in uforces since its garbage */ - uint ridx = dec_data_dim.y * dec_data_dim.x * uforces; - for (uint i = uforces; i < dec_data_dim.z; i++) { + + int direction = 1; + uint ridx = 0; + /* NOTE: For Each Acquistion in Raw Data */ + for (uint i = 0; i < dec_data_dim.z; i++) { uint base_idx = (i - uforces) / 4; uint sub_idx = (i - uforces) % 4; float transmit_dist = image_point.z; + /* NOTE: For Each Virtual Source */ for (uint j = 0; j < dec_data_dim.y; j++) { float dist = transmit_dist + length(rdist); float time = dist / speed_of_sound + time_correction; @@ -121,14 +120,17 @@ void main() a = a * a; vec2 p = cubic(ridx, time * sampling_frequency); + /* NOTE: tribal knowledge; this is a problem with the imaging sequence */ + if (i == 0) p *= inversesqrt(128); //p *= vec2(cos(iq_time_scale * time), sin(iq_time_scale * time)); sum += p; - rdist.y -= dy; - ridx += dec_data_dim.x; + + rdist[direction] -= delta[direction]; + ridx += dec_data_dim.x; } - rdist.y = starting_dist.y; - rdist.x -= dx; + rdist[direction] = starting_dist[direction]; + rdist[(~direction) & 1] -= delta[(~direction) & 1]; } float val = length(sum); imageStore(u_out_data_tex, out_coord, vec4(val, val, 0, 0)); diff --git a/shaders/demod.glsl b/shaders/demod.glsl @@ -16,9 +16,9 @@ layout(std140, binding = 0) uniform parameters { vec4 lpf_coefficients[16]; /* Low Pass Filter Cofficients */ uvec4 dec_data_dim; /* Samples * Channels * Acquisitions; last element ignored */ uvec4 output_points; /* Width * Height * Depth; last element ignored */ + vec4 output_min_coord; /* [m] Top left corner of output region */ + vec4 output_max_coord; /* [m] Bottom right corner of output region */ uvec2 rf_raw_dim; /* Raw Data Dimensions */ - vec2 output_min_xz; /* [m] Top left corner of output region */ - vec2 output_max_xz; /* [m] Bottom right corner of output region */ vec2 xdc_min_xy; /* [m] Min center of transducer elements */ vec2 xdc_max_xy; /* [m] Max center of transducer elements */ uint channel_offset; /* Offset into channel_mapping: 0 or 128 (rows or columns) */ @@ -29,7 +29,6 @@ layout(std140, binding = 0) uniform parameters { float focal_depth; /* [m] */ float time_offset; /* pulse length correction time [s] */ uint uforces; /* mode is UFORCES (1) or FORCES (0) */ - float off_axis_pos; /* Where on the 3rd axis to render the image (Hercules only) */ }; void main() diff --git a/shaders/hadamard.glsl b/shaders/hadamard.glsl @@ -20,9 +20,9 @@ layout(std140, binding = 0) uniform parameters { vec4 lpf_coefficients[16]; /* Low Pass Filter Cofficients */ uvec4 dec_data_dim; /* Samples * Channels * Acquisitions; last element ignored */ uvec4 output_points; /* Width * Height * Depth; last element ignored */ + vec4 output_min_coord; /* [m] Top left corner of output region */ + vec4 output_max_coord; /* [m] Bottom right corner of output region */ uvec2 rf_raw_dim; /* Raw Data Dimensions */ - vec2 output_min_xz; /* [m] Top left corner of output region */ - vec2 output_max_xz; /* [m] Bottom right corner of output region */ vec2 xdc_min_xy; /* [m] Min center of transducer elements */ vec2 xdc_max_xy; /* [m] Max center of transducer elements */ uint channel_offset; /* Offset into channel_mapping: 0 or 128 (rows or columns) */ @@ -33,7 +33,6 @@ layout(std140, binding = 0) uniform parameters { float focal_depth; /* [m] */ float time_offset; /* pulse length correction time [s] */ uint uforces; /* mode is UFORCES (1) or FORCES (0) */ - float off_axis_pos; /* Where on the 3rd axis to render the image (Hercules only) */ }; void main() diff --git a/shaders/min_max.glsl b/shaders/min_max.glsl @@ -3,11 +3,11 @@ /* NOTE: Does a binary search in 3D for smallest and largest output values */ #version 460 core -layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; +layout(local_size_x = 32, local_size_y = 1, local_size_z = 32) in; -layout(rg32f, location = 1) uniform image3D u_out_data_tex; -layout(rg32f, location = 2) uniform image3D u_mip_view_tex; -layout(location = 3) uniform int u_mip_map = 0; +layout(rg32f, location = 1) uniform readonly image3D u_out_data_tex; +layout(rg32f, location = 2) uniform writeonly image3D u_mip_view_tex; +layout(location = 3) uniform int u_mip_map = 0; void main() { diff --git a/shaders/render.glsl b/shaders/render.glsl @@ -19,16 +19,19 @@ vec3 hsv2rgb(vec3 hsv) void main() { ivec3 out_data_dim = textureSize(u_out_data_tex, 0); - ivec2 coord = ivec2(fragTexCoord * out_data_dim.xy); + + /* 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; - float smp = texelFetch(u_out_data_tex, ivec3(coord.x, coord.y, 0), 0).x; - float absmax = max(abs(min_max.y), abs(min_max.x)); + ivec3 smp_coord = ivec3(coord.x, 0, coord.y); + float smp = texelFetch(u_out_data_tex, smp_coord, 0).x; + float absmax = max(abs(min_max.y), abs(min_max.x)); smp = 20 * log(abs(smp) / absmax) / log(10); smp = clamp(smp, u_db_cutoff, 0) / u_db_cutoff; smp = 1 - smp; - //v_out_colour = vec4(hsv2rgb(vec3(360 * smp + 120, 0.8, 0.95)), 1); + //v_out_colour = vec4(hsv2rgb(vec3(360 * smp, 0.8, 0.95)), 1); v_out_colour = vec4(smp, smp, smp, 1); } diff --git a/shaders/uforces.glsl b/shaders/uforces.glsl @@ -1,6 +1,6 @@ /* See LICENSE for license details. */ #version 460 core -layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; +layout(local_size_x = 32, local_size_y = 1, local_size_z = 32) in; layout(std430, binding = 1) readonly restrict buffer buffer_1 { vec2 rf_data[]; @@ -12,9 +12,9 @@ layout(std140, binding = 0) uniform parameters { vec4 lpf_coefficients[16]; /* Low Pass Filter Cofficients */ uvec4 dec_data_dim; /* Samples * Channels * Acquisitions; last element ignored */ uvec4 output_points; /* Width * Height * Depth; last element ignored */ + vec4 output_min_coord; /* [m] Top left corner of output region */ + vec4 output_max_coord; /* [m] Bottom right corner of output region */ uvec2 rf_raw_dim; /* Raw Data Dimensions */ - vec2 output_min_xz; /* [m] Top left corner of output region */ - vec2 output_max_xz; /* [m] Bottom right corner of output region */ vec2 xdc_min_xy; /* [m] Min center of transducer elements */ vec2 xdc_max_xy; /* [m] Max center of transducer elements */ uint channel_offset; /* Offset into channel_mapping: 0 or 128 (rows or columns) */ @@ -25,10 +25,9 @@ layout(std140, binding = 0) uniform parameters { float focal_depth; /* [m] */ float time_offset; /* pulse length correction time [s] */ uint uforces; /* mode is UFORCES (1) or FORCES (0) */ - float off_axis_pos; /* Where on the 3rd axis to render the image (Hercules only) */ }; -layout(rg32f, location = 1) uniform image3D u_out_data_tex; +layout(rg32f, location = 1) writeonly uniform image3D u_out_data_tex; #define C_SPLINE 0.5 @@ -66,19 +65,17 @@ vec2 cubic(uint ridx, float x) void main() { - vec2 pixel = vec2(gl_GlobalInvocationID.xy); - ivec3 out_coord = ivec3(gl_GlobalInvocationID.xyz); + vec3 voxel = vec3(gl_GlobalInvocationID); + ivec3 out_coord = ivec3(gl_GlobalInvocationID); ivec3 out_data_dim = imageSize(u_out_data_tex); - /* NOTE: Convert pixel to physical coordinates */ + /* NOTE: Convert voxel to physical coordinates */ vec2 xdc_size = abs(xdc_max_xy - xdc_min_xy); - vec2 output_size = abs(output_max_xz - output_min_xz); - - /* TODO: for now assume y-dimension is along transducer center */ - vec3 image_point = vec3( - output_min_xz.x + pixel.x * output_size.x / out_data_dim.x, - 0, - output_min_xz.y + pixel.y * output_size.y / out_data_dim.y + vec4 output_size = abs(output_max_coord - output_min_coord); + vec3 image_point = vec3( + output_min_coord.x + voxel.x * output_size.x / out_data_dim.x, + output_min_coord.y + voxel.y * output_size.y / out_data_dim.y, + output_min_coord.z + voxel.z * output_size.z / out_data_dim.z ); /* NOTE: used for constant F# dynamic receive apodization. This is implemented as: @@ -88,7 +85,7 @@ void main() * \ |z_e - z_i|/ * * where x,z_e are transducer element positions and x,z_i are image positions. */ - float f_num = output_size.y / output_size.x; + float f_num = output_size.z / output_size.x; float apod_arg = f_num * 0.5 * radians(360) / abs(image_point.z); /* NOTE: for I-Q data phase correction */ @@ -96,7 +93,6 @@ void main() vec2 starting_dist = vec2(image_point.x - xdc_min_xy.x, image_point.z); float dx = xdc_size.x / float(dec_data_dim.y); - float dzsign = sign(image_point.z); /* NOTE: offset correcting for both pulse length and low pass filtering */ float time_correction = time_offset + lpf_order / sampling_frequency; @@ -109,7 +105,7 @@ void main() uint sub_idx = (i - uforces) % 4; vec3 focal_point = vec3(uforces_channels[base_idx][sub_idx] * dx + xdc_min_xy.x, 0, 0); - float transmit_dist = dzsign * distance(image_point, focal_point); + float transmit_dist = distance(image_point, focal_point); vec2 rdist = starting_dist; for (uint j = 0; j < dec_data_dim.y; j++) { diff --git a/ui.c b/ui.c @@ -331,8 +331,8 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) { BeamformerParameters *bp = &ctx->params->raw; - f32 minx = bp->output_min_xz.x + 1e-6, maxx = bp->output_max_xz.x - 1e-6; - f32 minz = bp->output_min_xz.y + 1e-6, maxz = bp->output_max_xz.y - 1e-6; + f32 minx = bp->output_min_coordinate.x + 1e-6, maxx = bp->output_max_coordinate.x - 1e-6; + f32 minz = bp->output_min_coordinate.z + 1e-6, maxz = bp->output_max_coordinate.z - 1e-6; Rect draw_r = r; draw_r.pos.y += 20; @@ -355,26 +355,22 @@ draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) draw_r = do_text_input_listing(s8("Speed of Sound:"), s8("[m/s]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_min_xz.x, (v2){.x = -1e3, .y = maxx}, 1e3, 1}; + bmv = (BPModifiableValue){&bp->output_min_coordinate.x, (v2){.x = -1e3, .y = maxx}, 1e3, 1}; draw_r = do_text_input_listing(s8("Min X Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_max_xz.x, (v2){.x = minx, .y = 1e3}, 1e3, 1}; + bmv = (BPModifiableValue){&bp->output_max_coordinate.x, (v2){.x = minx, .y = 1e3}, 1e3, 1}; draw_r = do_text_input_listing(s8("Max X Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_min_xz.y, (v2){.y = maxz}, 1e3, 1}; + bmv = (BPModifiableValue){&bp->output_min_coordinate.z, (v2){.y = maxz}, 1e3, 1}; draw_r = do_text_input_listing(s8("Min Z Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->output_max_xz.y, (v2){.x = minz, .y = 1e3}, 1e3, 1}; + bmv = (BPModifiableValue){&bp->output_max_coordinate.z, (v2){.x = minz, .y = 1e3}, 1e3, 1}; draw_r = do_text_input_listing(s8("Max Z Point:"), s8("[mm]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&bp->off_axis_pos, (v2){.x = minx * 2, .y = maxx * 2}, 1e3, 1}; - draw_r = do_text_input_listing(s8("Y Position:"), s8("[mm]"), bmv, ctx, arena, - draw_r, mouse, hover_t + idx++); - bmv = (BPModifiableValue){&ctx->fsctx.db, (v2){.x = -120}, 1, 0}; draw_r = do_text_input_listing(s8("Dynamic Range:"), s8("[dB]"), bmv, ctx, arena, draw_r, mouse, hover_t + idx++); @@ -479,8 +475,8 @@ draw_ui(BeamformerCtx *ctx, Arena arena) Texture *output = &ctx->fsctx.output.texture; v2 output_dim = { - .x = bp->output_max_xz.x - bp->output_min_xz.x, - .y = bp->output_max_xz.y - bp->output_min_xz.y, + .x = bp->output_max_coordinate.x - bp->output_min_coordinate.x, + .y = bp->output_max_coordinate.z - bp->output_min_coordinate.z, }; v2 mouse = { .rl = GetMousePosition() }; @@ -546,13 +542,16 @@ draw_ui(BeamformerCtx *ctx, Arena arena) Rect tick_rect = {.pos = start_pos, .size = vr.size}; tick_rect.size.E[!i] = 10 + tick_len + txt_s.x; + /* TODO: don't do this nonsense; this code will need to get + * split into a seperate function */ + u32 coord_idx = i == 0? 0 : 2; if (CheckCollisionPointRec(mouse.rl, tick_rect.rl)) { f32 scale[2] = {0.5e-3, 1e-3}; f32 size_delta = GetMouseWheelMove() * scale[i]; /* TODO: smooth scroll this? */ - if (i == 0) - bp->output_min_xz.E[i] -= size_delta; - bp->output_max_xz.E[i] += size_delta; + if (coord_idx== 0) + bp->output_min_coordinate.E[coord_idx] -= size_delta; + bp->output_max_coordinate.E[coord_idx] += size_delta; if (size_delta) { ctx->flags |= DO_COMPUTE; ctx->params->upload = 1; @@ -564,7 +563,7 @@ draw_ui(BeamformerCtx *ctx, Arena arena) } CLAMP01(txt_colour_t[i]); - f32 mm = bp->output_min_xz.E[i] * 1e3; + f32 mm = bp->output_min_coordinate.E[coord_idx] * 1e3; f32 mm_inc = inc * output_dim.E[i] * 1e3 / vr.size.E[i]; Color txt_colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, diff --git a/util.c b/util.c @@ -58,6 +58,13 @@ uv4_equal(uv4 a, uv4 b) return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; } +static u32 +round_down_power_of_2(u32 a) +{ + u32 result = 0x80000000UL >> _lzcnt_u32(a); + return result; +} + static void fill_hadamard(i32 *m, u32 dim) {