ogl_beamforming

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

Commit: c7cd4986b63922418730083ba1ed8c05bb497ebe
Parent: 8bd1e76f64dbcb6774967455319a3fb107dcc418
Author: Randy Palamar
Date:   Fri, 10 Oct 2025 15:16:48 -0600

shaders/das: add linear interpolation, use named interpolation modes

note: the if conditions in the switch cases are measurably faster
than the branchless thing before. Linear is most noticeable with a
4.3% improvement vs using the branchless computation

Diffstat:
Mbeamformer.c | 4+---
Mbeamformer.meta | 20+++++++++++++++++---
Mbeamformer_parameters.h | 2+-
Mgenerated/beamformer.meta.c | 42++++++++++++++++++++++++++++++------------
Mshaders/das.glsl | 21++++++++++++++++++---
Mtests/throughput.c | 6+++---
Mui.c | 7++++---
7 files changed, 74 insertions(+), 28 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -453,6 +453,7 @@ das_ubo_from_beamformer_parameters(BeamformerComputePlan *cp, BeamformerDASUBO * cp->das_bake.sample_count = bp->sample_count; cp->das_bake.channel_count = bp->channel_count; cp->das_bake.acquisition_count = bp->acquisition_count; + cp->das_bake.interpolation_mode = bp->interpolation_mode; cp->das_bake.transmit_angle = bp->focal_vector[0]; cp->das_bake.focus_depth = bp->focal_vector[1]; cp->das_bake.transmit_receive_orientation = bp->transmit_receive_orientation; @@ -470,9 +471,6 @@ das_ubo_from_beamformer_parameters(BeamformerComputePlan *cp, BeamformerDASUBO * if (bp->das_shader_id == BeamformerAcquisitionKind_HERO_PA) result |= BeamformerShaderDASFlags_ReceiveOnly; - if (bp->interpolate) - result |= BeamformerShaderDASFlags_Interpolate; - return result; } diff --git a/beamformer.meta b/beamformer.meta @@ -19,11 +19,16 @@ [HERO_PA HERO-PA 0] } -@Expand(AcquisitionKind) +@Table([name]) InterpolationMode { - @Enumeration(AcquisitionKind `$(name)`) + [Nearest] + [Linear] + [Cubic] } +@Expand(AcquisitionKind) @Enumeration(AcquisitionKind `$(name)`) +@Expand(InterpolationMode) @Enumeration(InterpolationMode `$(name)`) + @Emit { `read_only global u8 beamformer_acquisition_kind_has_fixed_transmits[] = {` @@ -38,6 +43,13 @@ `};` } +@Emit +{ + `read_only global s8 beamformer_interpolation_mode_strings[] = {` + @Expand(InterpolationMode) ` s8_comp("$(name)"),` + `};` +} + @ShaderGroup Compute { @Shader CudaDecode @@ -92,8 +104,9 @@ { @Enumeration(AcquisitionKind) @Enumeration(DataKind) + @Enumeration(InterpolationMode) @Enumeration(RCAOrientation) - @Flags([Fast Sparse Interpolate CoherencyWeighting ReceiveOnly SingleFocus SingleOrientation]) + @Flags([Fast Sparse CoherencyWeighting ReceiveOnly SingleFocus SingleOrientation]) @Bake { @@ -101,6 +114,7 @@ @BakeInt(AcquisitionKind acquisition_kind ) @BakeInt(ChannelCount channel_count ) @BakeInt(DataKind data_kind ) + @BakeInt(InterpolationMode interpolation_mode ) @BakeInt(SampleCount sample_count ) @BakeInt(TransmitReceiveOrientation transmit_receive_orientation) diff --git a/beamformer_parameters.h b/beamformer_parameters.h @@ -79,7 +79,7 @@ typedef enum {BEAMFORMER_CONSTANTS_LIST} BeamformerConstants; X(speed_of_sound, float, , single, 1, "[m/s]") \ X(f_number, float, , single, 1, "F# (set to 0 to disable)") \ X(off_axis_pos, float, , single, 1, "[m] Position on screen normal to beamform in TPW/VLS/HERCULES") \ - X(interpolate, uint32_t, , uint32, 1, "Perform Cubic Interpolation of RF Samples") \ + X(interpolation_mode, uint32_t, , uint32, 1, "Nearest, Linear, or Cubic Interpolation of RF Samples") \ X(coherency_weighting, uint32_t, , uint32, 1, "Apply coherency weighting to output data") \ X(beamform_plane, uint32_t, , uint32, 1, "Plane to Beamform in TPW/VLS/HERCULES") \ X(decimation_rate, uint32_t, , uint32, 1, "Number of times to decimate") diff --git a/generated/beamformer.meta.c b/generated/beamformer.meta.c @@ -45,6 +45,13 @@ typedef enum { } BeamformerAcquisitionKind; typedef enum { + BeamformerInterpolationMode_Nearest = 0, + BeamformerInterpolationMode_Linear = 1, + BeamformerInterpolationMode_Cubic = 2, + BeamformerInterpolationMode_Count, +} BeamformerInterpolationMode; + +typedef enum { BeamformerShaderDecodeFlags_DilateOutput = (1 << 0), } BeamformerShaderDecodeFlags; @@ -57,11 +64,10 @@ typedef enum { typedef enum { BeamformerShaderDASFlags_Fast = (1 << 0), BeamformerShaderDASFlags_Sparse = (1 << 1), - BeamformerShaderDASFlags_Interpolate = (1 << 2), - BeamformerShaderDASFlags_CoherencyWeighting = (1 << 3), - BeamformerShaderDASFlags_ReceiveOnly = (1 << 4), - BeamformerShaderDASFlags_SingleFocus = (1 << 5), - BeamformerShaderDASFlags_SingleOrientation = (1 << 6), + BeamformerShaderDASFlags_CoherencyWeighting = (1 << 2), + BeamformerShaderDASFlags_ReceiveOnly = (1 << 3), + BeamformerShaderDASFlags_SingleFocus = (1 << 4), + BeamformerShaderDASFlags_SingleOrientation = (1 << 5), } BeamformerShaderDASFlags; typedef enum { @@ -123,6 +129,7 @@ typedef union { u32 acquisition_kind; u32 channel_count; u32 data_kind; + u32 interpolation_mode; u32 sample_count; u32 transmit_receive_orientation; f32 demodulation_frequency; @@ -133,7 +140,7 @@ typedef union { f32 time_offset; f32 transmit_angle; }; - u32 E[13]; + u32 E[14]; } BeamformerShaderDASBakeParameters; read_only global s8 beamformer_shader_names[] = { @@ -223,6 +230,11 @@ read_only global s8 beamformer_shader_global_header_strings[] = { "#define AcquisitionKind_Flash 10\n" "#define AcquisitionKind_HERO_PA 11\n" "\n"), + s8_comp("" + "#define InterpolationMode_Nearest 0\n" + "#define InterpolationMode_Linear 1\n" + "#define InterpolationMode_Cubic 2\n" + "\n"), }; read_only global s8 *beamformer_shader_flag_strings[] = { @@ -237,7 +249,6 @@ read_only global s8 *beamformer_shader_flag_strings[] = { (s8 []){ s8_comp("Fast"), s8_comp("Sparse"), - s8_comp("Interpolate"), s8_comp("CoherencyWeighting"), s8_comp("ReceiveOnly"), s8_comp("SingleFocus"), @@ -251,7 +262,7 @@ read_only global s8 *beamformer_shader_flag_strings[] = { read_only global u8 beamformer_shader_flag_strings_count[] = { 1, 3, - 7, + 6, 0, 0, 0, @@ -260,7 +271,7 @@ read_only global u8 beamformer_shader_flag_strings_count[] = { read_only global i32 *beamformer_shader_header_vectors[] = { (i32 []){0, 1}, (i32 []){0, 3}, - (i32 []){4, 0, 2}, + (i32 []){4, 0, 5, 2}, 0, 0, 0, @@ -269,7 +280,7 @@ read_only global i32 *beamformer_shader_header_vectors[] = { read_only global i32 beamformer_shader_header_vector_lengths[] = { 2, 2, - 3, + 4, 0, 0, 0, @@ -306,6 +317,7 @@ read_only global s8 *beamformer_shader_bake_parameter_names[] = { s8_comp("AcquisitionKind"), s8_comp("ChannelCount"), s8_comp("DataKind"), + s8_comp("InterpolationMode"), s8_comp("SampleCount"), s8_comp("TransmitReceiveOrientation"), s8_comp("DemodulationFrequency"), @@ -324,7 +336,7 @@ read_only global s8 *beamformer_shader_bake_parameter_names[] = { read_only global u8 *beamformer_shader_bake_parameter_is_float[] = { (u8 []){0, 0, 0, 0, 0, 0, 0, 0, 0}, (u8 []){0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, - (u8 []){0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1}, + (u8 []){0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1}, 0, 0, 0, @@ -333,7 +345,7 @@ read_only global u8 *beamformer_shader_bake_parameter_is_float[] = { read_only global i32 beamformer_shader_bake_parameter_counts[] = { 9, 12, - 13, + 14, 0, 0, 0, @@ -369,3 +381,9 @@ read_only global s8 beamformer_acquisition_kind_strings[] = { s8_comp("HERO-PA"), }; +read_only global s8 beamformer_interpolation_mode_strings[] = { + s8_comp("Nearest"), + s8_comp("Linear"), + s8_comp("Cubic"), +}; + diff --git a/shaders/das.glsl b/shaders/das.glsl @@ -95,10 +95,25 @@ SAMPLE_TYPE cubic(const int base_index, const float index) SAMPLE_TYPE sample_rf(const int channel, const int transmit, const float index) { - SAMPLE_TYPE result = SAMPLE_TYPE(index >= 0.0f) * SAMPLE_TYPE((int(index) + 1 + Interpolate) < SampleCount); + SAMPLE_TYPE result = SAMPLE_TYPE(0); int base_index = int(channel * SampleCount * AcquisitionCount + transmit * SampleCount); - if (bool(Interpolate)) result *= cubic(base_index, index); - else result *= rf_data[base_index + int(round(index))]; + switch (InterpolationMode) { + case InterpolationMode_Nearest:{ + if (index >= 0 && int(round(index)) < SampleCount) + result = rf_data[base_index + int(round(index))]; + }break; + case InterpolationMode_Linear:{ + if (index >= 0 && round(index) < SampleCount) { + float tk, t = modf(index, tk); + int n = base_index + int(tk); + result = (1 - t) * rf_data[n] + t * rf_data[n + 1]; + } + }break; + case InterpolationMode_Cubic:{ + if (index >= 0 && (int(index) + 2) < SampleCount) + result = cubic(base_index, index); + }break; + } result = rotate_iq(result, index / SamplingFrequency); return result; } diff --git a/tests/throughput.c b/tests/throughput.c @@ -328,9 +328,9 @@ execute_study(s8 study, Arena arena, Stream path, Options *options) bp.output_max_coordinate[1] = 0; bp.output_max_coordinate[2] = g_axial_extent.y; - bp.f_number = g_f_number; - bp.beamform_plane = 0; - bp.interpolate = 1; + bp.f_number = g_f_number; + bp.beamform_plane = 0; + bp.interpolation_mode = BeamformerInterpolationMode_Cubic; bp.decimation_rate = 1; bp.demodulation_frequency = bp.sampling_frequency / 4; diff --git a/ui.c b/ui.c @@ -1215,10 +1215,11 @@ add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) add_beamformer_variable(ui, group, &ui->arena, s8("F#:"), s8(""), &bp->f_number, (v2){.y = 1e3f}, 1, 0.1f, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); - read_only local_persist s8 true_false_labels[] = {s8_comp("False"), s8_comp("True")}; - add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Interpolate:"), - &bp->interpolate, true_false_labels, countof(true_false_labels)); + add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Interpolation:"), + &bp->interpolation_mode, beamformer_interpolation_mode_strings, + countof(beamformer_interpolation_mode_strings)); + read_only local_persist s8 true_false_labels[] = {s8_comp("False"), s8_comp("True")}; add_variable_cycler(ui, group, &ui->arena, V_CAUSES_COMPUTE, ui->font, s8("Coherency Weighting:"), &bp->coherency_weighting, true_false_labels, countof(true_false_labels));