ogl_beamforming

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

Commit: 34fd201e170e415c0b021995bba191e2783b3938
Parent: 8b2e08c3def0f7c6fcc1166a27c802b1e2dd2308
Author: Randy Palamar
Date:   Thu, 15 Oct 2026 20:00:54 -0700

das: checkpoint on multi-tile beamforming

Diffstat:
Mbeamformer.meta | 20++++++++++++--------
Mbeamformer_core.c | 22++++++++++++----------
Mgenerated/beamformer.c | 58++++++++++++++++++++++++++++++++++------------------------
Mshaders/das.glsl | 46+++++++++++++++++++++++++++++++---------------
Mutil.c | 10++++++++++
5 files changed, 99 insertions(+), 57 deletions(-)

diff --git a/beamformer.meta b/beamformer.meta @@ -430,42 +430,46 @@ [Hadamard U32] [IncoherentFrame U32] [SparseElements U32] + [TransducerTransforms U32] [TransmitReceiveOrientations U32] [AcquisitionKind U32] [AcquisitionCount S32] - [ReceiveChannelCount S32] - [ChunkChannelCount S32] [SampleCount S32] [AcquisitionByteStride U32] [ChannelByteStride U32] + [ReceiveChannelCount U16] + [TransmitChannelCount U16] + [ReceiveTileCount U16] + [TransmitTileCount U16] + [ChunkChannelCount U16] + [TransmitReceiveOrientation U16] + + [InterpolationMode U16] + + [ReadiGroupCount U16] [SamplingFrequency F32] [DemodulationFrequency F32] [SpeedOfSound F32] [TimeOffset F32] - [InterpolationMode U32] [FNumber F32] - [TransmitReceiveOrientation U32] [FocusDepth F32] [TransmitAngle F32] [OutputSizeX U32] [OutputSizeY U32] [OutputSizeZ U32] - - [ReadiGroupCount U32] } @PushConstants { - [xdc_transform M4] [voxel_transform M4] [xdc_element_pitch V2] [rf_data U64] [output_frame U64] - [channel_offset S32] + [channel_offset U32] [readi_group U32] } } diff --git a/beamformer_core.c b/beamformer_core.c @@ -924,13 +924,19 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb, A db->FNumber = pb->parameters.f_number; db->AcquisitionKind = pb->parameters.acquisition_kind; db->SampleCount = node->rf_dimensions.E[BeamformerRFDimension_Samples]; - db->ReceiveChannelCount = pb->parameters.channel_count; db->AcquisitionCount = node->rf_dimensions.E[BeamformerRFDimension_Events]; db->ChunkChannelCount = node->rf_dimensions.E[BeamformerRFDimension_Channels]; db->ChannelByteStride = input_stride.E[BeamformerRFDimension_Channels] * beamformer_data_kind_byte_size[node->input_data_kind]; db->AcquisitionByteStride = input_stride.E[BeamformerRFDimension_Events] * beamformer_data_kind_byte_size[node->input_data_kind]; + + // TODO(rnp): pass these in + db->ReceiveChannelCount = pb->parameters.channel_count; + db->TransmitChannelCount = pb->parameters.channel_count; + db->ReceiveTileCount = 1; + db->TransmitTileCount = 1; + db->InterpolationMode = pb->parameters.interpolation_mode; db->TransmitAngle = pb->parameters.focal_vector.E[0]; db->FocusDepth = pb->parameters.focal_vector.E[1]; @@ -940,9 +946,6 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb, A db->OutputSizeZ = cp->output_points.z; db->TransmitReceiveOrientation = pb->parameters.transmit_receive_orientation; - // NOTE(rnp): old gcc will miscompile an assignment - memory_copy(cp->xdc_transform.E, pb->parameters.xdc_transform.E, sizeof(cp->xdc_transform)); - cp->voxel_transform = m4_mul(cp->ui_voxel_transform, pb->parameters.das_voxel_transform); cp->xdc_element_pitch = pb->parameters.xdc_element_pitch; @@ -963,32 +966,33 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb, A sd->layout = layout_for_output(cp->output_points); sd->dispatch = dispatch_for_output(sd->layout, cp->output_points); + sd->uses_heap = 1; + db->TransducerTransforms = gpu_resource_push(resource_builder, m4, db->TransmitTileCount * db->ReceiveTileCount, + .data = &pb->parameters.xdc_transform, + .name = str8("transducer_transforms")); + if (!single_focus) { db->FocalVectors = gpu_resource_push(resource_builder, v2, db->AcquisitionCount, .data = pb->focal_vectors, .name = str8("focal_vectors")); - sd->uses_heap = 1; } if (!single_orientation) { db->TransmitReceiveOrientations = gpu_resource_push(resource_builder, u8, db->AcquisitionCount, .data = pb->transmit_receive_orientations, .name = str8("transmit_receive_orientations")); - sd->uses_heap = 1; } if (sparse) { db->SparseElements = gpu_resource_push(resource_builder, i16, db->AcquisitionCount, .data = pb->sparse_elements, .name = str8("sparse_elements")); - sd->uses_heap = 1; } if (pb->parameters.coherency_weighting) { db->IncoherentFrame = gpu_resource_push(resource_builder, u32, 0, .size = beamformer_incoherent_frame_byte_size(cp->output_points, das_data_kind), .name = str8("incoherent_buffer")); - sd->uses_heap = 1; } cp->readi_group = pb->parameters.readi_group; @@ -997,7 +1001,6 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb, A db->Hadamard = gpu_resource_push(resource_builder, f16, order * order, .data = make_hadamard_transpose(scratch, order, 0), .name = str8("readi_hadamard")); - sd->uses_heap = 1; } }break; @@ -1333,7 +1336,6 @@ do_compute_shader(GPUCommandList cmd, BeamformerComputePlan *cp, BeamformerFrame .readi_group = cp->readi_group, }; memory_copy(pc.voxel_transform.E, cp->das_voxel_transform.E, sizeof(pc.voxel_transform)); - memory_copy(pc.xdc_transform.E, cp->xdc_transform.E, sizeof(pc.xdc_transform)); gpu_command_push_constants(cmd, 0, sizeof(pc), &pc); gpu_command_dispatch_compute(cmd, dispatch); diff --git a/generated/beamformer.c b/generated/beamformer.c @@ -215,27 +215,31 @@ typedef struct { u32 Hadamard; u32 IncoherentFrame; u32 SparseElements; + u32 TransducerTransforms; u32 TransmitReceiveOrientations; u32 AcquisitionKind; i32 AcquisitionCount; - i32 ReceiveChannelCount; - i32 ChunkChannelCount; i32 SampleCount; u32 AcquisitionByteStride; u32 ChannelByteStride; + u16 ReceiveChannelCount; + u16 TransmitChannelCount; + u16 ReceiveTileCount; + u16 TransmitTileCount; + u16 ChunkChannelCount; + u16 TransmitReceiveOrientation; + u16 InterpolationMode; + u16 ReadiGroupCount; f32 SamplingFrequency; f32 DemodulationFrequency; f32 SpeedOfSound; f32 TimeOffset; - u32 InterpolationMode; f32 FNumber; - u32 TransmitReceiveOrientation; f32 FocusDepth; f32 TransmitAngle; u32 OutputSizeX; u32 OutputSizeY; u32 OutputSizeZ; - u32 ReadiGroupCount; } BeamformerDASBakeParameters; typedef struct { @@ -267,12 +271,11 @@ typedef struct { } BeamformerFilterPushConstants; typedef struct { - m4 xdc_transform; m4 voxel_transform; v2 xdc_element_pitch; u64 rf_data; u64 output_frame; - i32 channel_offset; + u32 channel_offset; u32 readi_group; } BeamformerDASPushConstants; @@ -521,22 +524,26 @@ read_only MetaStructMember *meta_struct_members_by_id[] = { {18, 12, 1, 0}, {18, 16, 1, 0}, {18, 20, 1, 0}, - {10, 24, 1, 0}, + {18, 24, 1, 0}, {10, 28, 1, 0}, {10, 32, 1, 0}, - {10, 36, 1, 0}, + {18, 36, 1, 0}, {18, 40, 1, 0}, - {18, 44, 1, 0}, - {8, 48, 1, 0}, - {8, 52, 1, 0}, - {8, 56, 1, 0}, + {19, 44, 1, 0}, + {19, 46, 1, 0}, + {19, 48, 1, 0}, + {19, 50, 1, 0}, + {19, 52, 1, 0}, + {19, 54, 1, 0}, + {19, 56, 1, 0}, + {19, 58, 1, 0}, {8, 60, 1, 0}, - {18, 64, 1, 0}, + {8, 64, 1, 0}, {8, 68, 1, 0}, - {18, 72, 1, 0}, + {8, 72, 1, 0}, {8, 76, 1, 0}, {8, 80, 1, 0}, - {18, 84, 1, 0}, + {8, 84, 1, 0}, {18, 88, 1, 0}, {18, 92, 1, 0}, {18, 96, 1, 0}, @@ -593,27 +600,31 @@ read_only str8 *meta_struct_member_names_by_id[] = { str8_comp("Hadamard"), str8_comp("IncoherentFrame"), str8_comp("SparseElements"), + str8_comp("TransducerTransforms"), str8_comp("TransmitReceiveOrientations"), str8_comp("AcquisitionKind"), str8_comp("AcquisitionCount"), - str8_comp("ReceiveChannelCount"), - str8_comp("ChunkChannelCount"), str8_comp("SampleCount"), str8_comp("AcquisitionByteStride"), str8_comp("ChannelByteStride"), + str8_comp("ReceiveChannelCount"), + str8_comp("TransmitChannelCount"), + str8_comp("ReceiveTileCount"), + str8_comp("TransmitTileCount"), + str8_comp("ChunkChannelCount"), + str8_comp("TransmitReceiveOrientation"), + str8_comp("InterpolationMode"), + str8_comp("ReadiGroupCount"), str8_comp("SamplingFrequency"), str8_comp("DemodulationFrequency"), str8_comp("SpeedOfSound"), str8_comp("TimeOffset"), - str8_comp("InterpolationMode"), str8_comp("FNumber"), - str8_comp("TransmitReceiveOrientation"), str8_comp("FocusDepth"), str8_comp("TransmitAngle"), str8_comp("OutputSizeX"), str8_comp("OutputSizeY"), str8_comp("OutputSizeZ"), - str8_comp("ReadiGroupCount"), }, (str8 []){ str8_comp("IncoherentSum"), @@ -636,7 +647,7 @@ read_only str8 *meta_struct_member_names_by_id[] = { read_only MetaStructInfo meta_struct_info_by_id[] = { {str8_comp("DecodeBakeParameters"), 11, 44, 0}, {str8_comp("FilterBakeParameters"), 13, 52, 0}, - {str8_comp("DASBakeParameters"), 25, 100, 0}, + {str8_comp("DASBakeParameters"), 29, 100, 0}, {str8_comp("CoherencyWeightingBakeParameters"), 3, 12, 0}, {str8_comp("ReshapeBakeParameters"), 9, 36, 0}, }; @@ -764,12 +775,11 @@ read_only str8 beamformer_shader_global_header_strings[] = { "\n"), str8_comp("" "layout(push_constant, std430) uniform PushConstants {\n" - " f32mat4 xdc_transform;\n" " f32mat4 voxel_transform;\n" " f32vec2 xdc_element_pitch;\n" " uint64_t rf_data;\n" " uint64_t output_frame;\n" - " int32_t channel_offset;\n" + " uint32_t channel_offset;\n" " uint32_t readi_group;\n" "};\n" "\n"), diff --git a/shaders/das.glsl b/shaders/das.glsl @@ -31,12 +31,6 @@ #define RESULT_STORE(a) (a) #endif -// NOTE(rnp): we don't want das to get recompiled when it isn't actually using the Heap -// but we also don't want to check everywhere in here for the existence of the Heap -#ifndef HeapBase - #define HeapBase u64(0) -#endif - layout(std430, buffer_reference) readonly buffer Input { InputDataType x[]; }; layout(std430, buffer_reference) buffer Output { OutputDataType x[]; }; @@ -52,6 +46,9 @@ layout(std430, buffer_reference) readonly buffer F32V4 { f32vec4 x[]; }; layout(std430, buffer_reference) readonly buffer F16V2 { f16vec2 x[]; }; layout(std430, buffer_reference) readonly buffer F16V4 { f16vec4 x[]; }; +// NOTE(rnp): don't mark this readonly, scalar loads perform worse where we are using this +layout(std430, buffer_reference) buffer M4 { mat4 x[]; }; + #define RX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 0, 4) #define TX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 4, 4) @@ -153,7 +150,7 @@ SAMPLE_TYPE sample_rf(const u64 rf_pointer, const f32 index) { SAMPLE_TYPE result = SAMPLE_TYPE(0); - switch (InterpolationMode) { + switch (u32(InterpolationMode)) { case InterpolationMode_Nearest:{ if (index >= 0.f && index < (f32(SampleCount) - 0.5f)) result = rotate_iq(Input(rf_pointer + InputDataKindByteSize * u32(round(index))).x[0], index / SamplingFrequency); @@ -261,9 +258,23 @@ f32 rca_transmit_distance(const vec3 world_point, const vec2 focal_vector, const return result; } +mat4 transducer_transform(const u32 acquisition) +{ + const u32 receive_tile_channels = ReceiveChannelCount / ReceiveTileCount; + const u32 receive_index = channel_offset / receive_tile_channels; + + const u32 transmit_tile_channels = TransmitChannelCount / TransmitTileCount; + const u32 transmit_index = acquisition / transmit_tile_channels; + + mat4 result = M4(HeapBase + TransducerTransforms).x[transmit_index * ReceiveTileCount + receive_index]; + return result; +} + RESULT_TYPE RCA(const vec3 world_point) { RESULT_TYPE result = RESULT_TYPE(0); + + const mat4 xdc_transform = transducer_transform(0); for (s32 acquisition = 0; acquisition < s32(AcquisitionCount); acquisition++) { const u8 tx_rx_orientation = tx_rx_orientation_for_acquisition(acquisition); const bool rx_rows = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows; @@ -292,6 +303,9 @@ RESULT_TYPE RCA(const vec3 world_point) RESULT_TYPE HERCULES(const vec3 world_point) { + // TODO(rnp): this whole thing needs have an outer loop over transmit tiles + const mat4 xdc_transform = transducer_transform(0); + const u8 tx_rx_orientation = tx_rx_orientation_for_acquisition(0); const bool rx_cols = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Columns; const vec2 focal_vector = focal_vector_for_acquisition(0); @@ -343,11 +357,12 @@ RESULT_TYPE FORCES(const vec3 world_point) { RESULT_TYPE result = RESULT_TYPE(0); - const u8 tx_rx_orientation = tx_rx_orientation_for_acquisition(0); - const s32 rx_axis = s32(RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows); - const f32 element_pitch = xdc_element_pitch[rx_axis]; + const mat4 xdc_transform = transducer_transform(0); + const u8 tx_rx_orientation = tx_rx_orientation_for_acquisition(0); + const u32 rx_axis = u32(RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows); + const f32 element_pitch = xdc_element_pitch[rx_axis]; // TODO(rnp): the sign of the origin offset might be flipped - const f32 origin_offset = FocusDepth * tan(radians(TransmitAngle)); + const f32 origin_offset = FocusDepth * tan(radians(TransmitAngle)); vec3 xdc_world_point = (xdc_transform * vec4(world_point, 1)).xyz; f32 lateral_coord = xdc_world_point[rx_axis]; @@ -396,11 +411,12 @@ RESULT_TYPE READI_FORCES(const vec3 world_point) { RESULT_TYPE result = RESULT_TYPE(0); - const u8 tx_rx_orientation = tx_rx_orientation_for_acquisition(0); - const s32 rx_axis = s32(RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows); - const f32 element_pitch = xdc_element_pitch[rx_axis]; + const mat4 xdc_transform = transducer_transform(0); + const u8 tx_rx_orientation = tx_rx_orientation_for_acquisition(0); + const u32 rx_axis = u32(RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows); + const f32 element_pitch = xdc_element_pitch[rx_axis]; // TODO(rnp): the sign of the origin offset might be flipped - const f32 origin_offset = FocusDepth * tan(radians(TransmitAngle)); + const f32 origin_offset = FocusDepth * tan(radians(TransmitAngle)); vec3 xdc_world_point = (xdc_transform * vec4(world_point, 1)).xyz; f32 lateral_coord = xdc_world_point[rx_axis]; diff --git a/util.c b/util.c @@ -647,6 +647,11 @@ stream_append_struct_member(Stream *s, const MetaStructMember *m, const void *st memory_copy(&value, ((u8 *)struct_base + m->offset), sizeof(value)); stream_append_str8(s, value ? str8("True") : str8("False")); }break; + case MetaKind_U16:{ + u16 value; + memory_copy(&value, ((u8 *)struct_base + m->offset), sizeof(value)); + stream_append_u64(s, value); + }break; case MetaKind_U32:{ u32 value; memory_copy(&value, ((u8 *)struct_base + m->offset), sizeof(value)); @@ -658,6 +663,11 @@ stream_append_struct_member(Stream *s, const MetaStructMember *m, const void *st stream_append_str8(s, str8("0x")); stream_append_hex_u64(s, value); }break; + case MetaKind_S16:{ + i16 value; + memory_copy(&value, ((u8 *)struct_base + m->offset), sizeof(value)); + stream_append_i64(s, value); + }break; case MetaKind_S32:{ i32 value; memory_copy(&value, ((u8 *)struct_base + m->offset), sizeof(value));