ogl_beamforming

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

das.glsl (19032B)


      1 /* See LICENSE for license details. */
      2 #if   InputDataKind == DataKind_Float32 || InputDataKind == DataKind_Float16
      3   #if CoherencyWeighting
      4     #define RESULT_TYPE               vec2
      5     #define RESULT_COHERENT_CAST(a)   (a).x
      6     #define RESULT_INCOHERENT_CAST(a) (a).y
      7   #endif
      8   #define SAMPLE_TYPE f32
      9 #elif InputDataKind == DataKind_Float32Complex || InputDataKind == DataKind_Float16Complex
     10   #if CoherencyWeighting
     11     #define RESULT_TYPE               vec3
     12     #define RESULT_COHERENT_CAST(a)   (a).xy
     13     #define RESULT_INCOHERENT_CAST(a) (a).z
     14   #endif
     15   #define SAMPLE_TYPE f32vec2
     16 #else
     17   #error InputDataKind unsupported for DAS
     18 #endif
     19 
     20 #ifndef RESULT_TYPE
     21   #define RESULT_TYPE SAMPLE_TYPE
     22 #endif
     23 
     24 #ifndef RESULT_COHERENT_CAST
     25   #define RESULT_COHERENT_CAST(a) (a)
     26 #endif
     27 
     28 #if CoherencyWeighting
     29   #define RESULT_STORE(a) RESULT_TYPE(RESULT_COHERENT_CAST(a), length(a))
     30 #else
     31   #define RESULT_STORE(a) (a)
     32 #endif
     33 
     34 layout(std430, buffer_reference) readonly buffer Input  { InputDataType  x[]; };
     35 layout(std430, buffer_reference)          buffer Output { OutputDataType x[]; };
     36 
     37 layout(std430, buffer_reference) buffer IncoherentOutput { f32 x[]; };
     38 
     39 layout(std430, buffer_reference) readonly buffer F16   { f16     x[]; };
     40 layout(std430, buffer_reference) readonly buffer F32   { f32     x[]; };
     41 layout(std430, buffer_reference) readonly buffer S16   { s16     x[]; };
     42 layout(std430, buffer_reference) readonly buffer U8    { u8      x[]; };
     43 layout(std430, buffer_reference) readonly buffer U32V4 { u32vec4 x[]; };
     44 layout(std430, buffer_reference) readonly buffer F32V2 { f32vec2 x[]; };
     45 layout(std430, buffer_reference) readonly buffer F32V4 { f32vec4 x[]; };
     46 layout(std430, buffer_reference) readonly buffer F16V2 { f16vec2 x[]; };
     47 layout(std430, buffer_reference) readonly buffer F16V4 { f16vec4 x[]; };
     48 
     49 // NOTE(rnp): don't mark this readonly, scalar loads perform worse where we are using this
     50 layout(std430, buffer_reference)          buffer M4    { mat4    x[]; };
     51 
     52 #define RX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 0, 4)
     53 #define TX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 4, 4)
     54 
     55 const f32 C_SPLINE = 0.5f;
     56 
     57 #if InputDataKind == DataKind_Float32Complex || InputDataKind == DataKind_Float16Complex
     58 vec2 rotate_iq(const vec2 iq, const float time)
     59 {
     60 	float arg    = radians(360) * DemodulationFrequency * time;
     61 	mat2  phasor = mat2( cos(arg), sin(arg),
     62 	                    -sin(arg), cos(arg));
     63 	vec2 result = phasor * iq;
     64 	return result;
     65 }
     66 #else
     67   #define rotate_iq(a, b) (a)
     68 #endif
     69 
     70 // NOTE(rnp): while the input RF buffer is padded such that we could continue reading
     71 // DAS is very expensive so we want to avoid any extra work possible.
     72 u32 batch_channel_count()
     73 {
     74 	const bool safe   = (ReceiveChannelCount % ChunkChannelCount) == 0;
     75 	const u32  result = safe ? ChunkChannelCount : min(ReceiveChannelCount - channel_offset, ChunkChannelCount);
     76 	return result;
     77 }
     78 
     79 u64 rf_data_pointer(const u32 channel, const u32 acquisition)
     80 {
     81 	u64 result  = rf_data + ChannelByteStride * channel + AcquisitionByteStride * acquisition;
     82 	result     -= InputDataKindByteSize * u32(InterpolationMode == InterpolationMode_Cubic);
     83 	return result;
     84 }
     85 
     86 /* NOTE: See: https://cubic.org/docs/hermite.htm */
     87 SAMPLE_TYPE cubic(const u64 rf_pointer, const f32 t)
     88 {
     89 	#if InputDataKind == DataKind_Float32
     90 		f32vec4 samples = F32V4(rf_pointer).x[0];
     91 	#elif InputDataKind == DataKind_Float16
     92 		f16vec4 samples = F16V4(rf_pointer).x[0];
     93 	#elif InputDataKind == DataKind_Float16Complex
     94 		f32vec2 samples[4];
     95 		uvec4 load = U32V4(rf_pointer).x[0];
     96 		samples[0] = unpackHalf2x16(load[0]);
     97 		samples[1] = unpackHalf2x16(load[1]);
     98 		samples[2] = unpackHalf2x16(load[2]);
     99 		samples[3] = unpackHalf2x16(load[3]);
    100 	#else
    101 		f32vec2 samples[4];
    102 		vec4 load1 = F32V4(rf_pointer).x[0];
    103 		vec4 load2 = F32V4(rf_pointer).x[1];
    104 		samples[0] = load1.xy;
    105 		samples[1] = load1.zw;
    106 		samples[2] = load2.xy;
    107 		samples[3] = load2.zw;
    108 	#endif
    109 
    110 	SAMPLE_TYPE result;
    111 	if (C_SPLINE == 0.5f && InputDataKind != DataKind_Float16Complex) {
    112 		// NOTE(rnp): expanded Catmull-Rom spline
    113 		// (mathematically equivalent but easier on the compiler)
    114 		SAMPLE_TYPE P0 = samples[0];
    115 		SAMPLE_TYPE P1 = samples[1];
    116 		SAMPLE_TYPE P2 = samples[2];
    117 		SAMPLE_TYPE P3 = samples[3];
    118 
    119 		SAMPLE_TYPE a = 0.5f * (-P0 + 3.f * (P1 - P2) + P3);
    120 		SAMPLE_TYPE b = P0 - 2.5f * P1 + 2.f * P2 - 0.5f * P3;
    121 		SAMPLE_TYPE c = 0.5f * (P2 - P0);
    122 
    123 		result = ((a * t + b) * t + c) * t + P1;
    124 	} else {
    125 		const mat4 h = mat4(
    126 			 2, -3,  0, 1,
    127 			-2,  3,  0, 0,
    128 			 1, -2,  1, 0,
    129 			 1, -1,  0, 0
    130 		);
    131 
    132 		vec4        Sh = vec4(t * t * t, t * t, t, 1) * h;
    133 		SAMPLE_TYPE P1 = samples[1];
    134 		SAMPLE_TYPE P2 = samples[2];
    135 		SAMPLE_TYPE T1 = C_SPLINE * (P2 - samples[0]);
    136 		SAMPLE_TYPE T2 = C_SPLINE * (samples[3] - P1);
    137 
    138 		#if   InputDataKind == DataKind_Float32 || InputDataKind == DataKind_Float16
    139 			result = dot(Sh, vec4(P1, P2, T1, T2));
    140 		#else
    141 			mat2x4 C = mat2x4(vec4(P1.x, P2.x, T1.x, T2.x), vec4(P1.y, P2.y, T1.y, T2.y));
    142 			result = Sh * C;
    143 		#endif
    144 	}
    145 
    146 	return result;
    147 }
    148 
    149 SAMPLE_TYPE sample_rf(const u64 rf_pointer, const f32 index)
    150 {
    151 	SAMPLE_TYPE result = SAMPLE_TYPE(0);
    152 
    153 	switch (u32(InterpolationMode)) {
    154 	case InterpolationMode_Nearest:{
    155 		if (index >= 0.f && index < (f32(SampleCount) - 0.5f))
    156 			result = rotate_iq(Input(rf_pointer + InputDataKindByteSize * u32(round(index))).x[0], index / SamplingFrequency);
    157 	}break;
    158 	case InterpolationMode_Linear:{
    159 		if (index >= 0.f && index < f32(SampleCount - 1)) {
    160 			#if InputDataKind == DataKind_Float32
    161 				f32vec2 rf = F32V2(rf_pointer + InputDataKindByteSize * u32(index)).x[0];
    162 			#elif InputDataKind == DataKind_Float16
    163 				f16vec2 rf = F16V2(rf_pointer + InputDataKindByteSize * u32(index)).x[0];
    164 			#elif InputDataKind == DataKind_Float16Complex
    165 				f16vec4 load  = F16V4(rf_pointer + InputDataKindByteSize * u32(index)).x[0];
    166 				f16vec2 rf[2] = {load.xy, load.zw};
    167 			#else
    168 				f32vec4 load  = F32V4(rf_pointer + InputDataKindByteSize * u32(index)).x[0];
    169 				f32vec2 rf[2] = {load.xy, load.zw};
    170 			#endif
    171 
    172 			f32 t  = fract(index);
    173 			result = (1 - t) * rf[0] + t * rf[1];
    174 			result = rotate_iq(result, index / SamplingFrequency);
    175 		}
    176 	}break;
    177 	case InterpolationMode_Cubic:{
    178 		if (index >= 1.f && index < f32(SampleCount - 2))
    179 			result = rotate_iq(cubic(rf_pointer + InputDataKindByteSize * u32(index), fract(index)), index / SamplingFrequency);
    180 	}break;
    181 	}
    182 	return result;
    183 }
    184 
    185 float sample_index(const float distance)
    186 {
    187 	float  time = distance / SpeedOfSound + TimeOffset;
    188 	return time * SamplingFrequency;
    189 }
    190 
    191 u32 output_index(const u32 x, const u32 y, const u32 z)
    192 {
    193 	u32 result = OutputSizeX * OutputSizeY * z + OutputSizeX * y + x;
    194 	return result;
    195 }
    196 
    197 float apodize(const float arg)
    198 {
    199 	/* IMPORTANT: do not move calculation of arg into this function. It will generate a
    200 	 * conditional move resulting in cos always being evaluated causing a slowdown */
    201 
    202 	/* NOTE: constant F# dynamic receive apodization. This is implemented as:
    203 	 *
    204 	 *                  /        |x_e - x_i|\
    205 	 *    a(x, z) = cos(F# * π * ----------- ) ^ 2
    206 	 *                  \        |z_e - z_i|/
    207 	 *
    208 	 * where x,z_e are transducer element positions and x,z_i are image positions. */
    209 	float a = cos(radians(180) * arg);
    210 	return a * a;
    211 }
    212 
    213 vec2 rca_plane_projection(const vec3 point, const bool rows)
    214 {
    215 	vec2 result = vec2(point[int(rows)], point[2]);
    216 	return result;
    217 }
    218 
    219 float plane_wave_transmit_distance(const vec3 point, const float transmit_angle, const bool tx_rows)
    220 {
    221 	return dot(rca_plane_projection(point, tx_rows), vec2(sin(transmit_angle), cos(transmit_angle)));
    222 }
    223 
    224 float cylindrical_wave_transmit_distance(const vec3 point, const float focal_depth,
    225                                          const float transmit_angle, const bool tx_rows)
    226 {
    227 	vec2 f = focal_depth * vec2(sin(transmit_angle), cos(transmit_angle));
    228 	return distance(rca_plane_projection(point, tx_rows), f);
    229 }
    230 
    231 f32 rca_transmit_distance(const vec3 world_point, const vec2 focal_vector, const u8 transmit_receive_orientation)
    232 {
    233 	float result = 0;
    234 	if (TX_ORIENTATION(transmit_receive_orientation) != RCAOrientation_None) {
    235 		bool  tx_rows        = TX_ORIENTATION(transmit_receive_orientation) == RCAOrientation_Rows;
    236 		float transmit_angle = radians(focal_vector.x);
    237 		float focal_depth    = focal_vector.y;
    238 
    239 		if (isinf(focal_depth)) {
    240 			result = plane_wave_transmit_distance(world_point, transmit_angle, tx_rows);
    241 		} else {
    242 			result = cylindrical_wave_transmit_distance(world_point, focal_depth, transmit_angle, tx_rows);
    243 		}
    244 	}
    245 	return result;
    246 }
    247 
    248 mat4 transducer_transform(const u32 acquisition)
    249 {
    250 	const u32 receive_tile_channels = ReceiveChannelCount / ReceiveTileCount;
    251 	const u32 receive_index = channel_offset / receive_tile_channels;
    252 
    253 	const u32 transmit_tile_channels = TransmitChannelCount / TransmitTileCount;
    254 	const u32 transmit_index = acquisition / transmit_tile_channels;
    255 
    256 	mat4 result = M4(HeapBase + TransducerTransforms).x[transmit_index * ReceiveTileCount + receive_index];
    257 	return result;
    258 }
    259 
    260 RESULT_TYPE RCA(const vec3 world_point)
    261 {
    262 	RESULT_TYPE result = RESULT_TYPE(0);
    263 
    264 	const mat4 xdc_transform = transducer_transform(0);
    265 	for (s32 acquisition = 0; acquisition < s32(AcquisitionCount); acquisition++) {
    266 		const u8   tx_rx_orientation = U8(HeapBase + TransmitReceiveOrientations).x[acquisition];
    267 		const bool rx_rows           = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows;
    268 		const vec2 focal_vector      = F32V2(HeapBase + FocalVectors).x[acquisition];
    269 
    270 		vec2  xdc_world_point = rca_plane_projection((xdc_transform * vec4(world_point, 1)).xyz, rx_rows);
    271 		f32   transmit_index  = sample_index(rca_transmit_distance(world_point, focal_vector, tx_rx_orientation));
    272 
    273 		u64 rf_pointer = rf_data_pointer(0u, u32(acquisition));
    274 
    275 		for (f32 chunk_channel = 0.f; chunk_channel < f32(batch_channel_count()); chunk_channel += 1.f) {
    276 			f32  rx_channel     = f32(channel_offset) + chunk_channel;
    277 			vec3 rx_center      = vec3(rx_channel * xdc_element_pitch, 0);
    278 			vec2 receive_vector = xdc_world_point - rca_plane_projection(rx_center, rx_rows);
    279 			f32  a_arg          = abs(FNumber * receive_vector.x / abs(xdc_world_point.y));
    280 
    281 			if (a_arg < 0.5f) {
    282 				f32         index = transmit_index + length(receive_vector) * SamplingFrequency / SpeedOfSound;
    283 				SAMPLE_TYPE value = apodize(a_arg) * sample_rf(rf_pointer, index);
    284 				result += RESULT_STORE(value);
    285 			}
    286 			rf_pointer += ChannelByteStride;
    287 		}
    288 	}
    289 	return result;
    290 }
    291 
    292 RESULT_TYPE HERCULES(const vec3 world_point)
    293 {
    294 	// TODO(rnp): this whole thing needs have an outer loop over transmit tiles
    295 	const mat4 xdc_transform   = transducer_transform(0);
    296 	const bool rx_cols         = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Columns;
    297 	const vec2 focal_vector    = f32vec2(steering_angle, focus_depth);
    298 	const vec3 xdc_world_point = (xdc_transform * vec4(world_point, 1)).xyz;
    299 
    300 	const f32 transmit_index   = sample_index(rca_transmit_distance(world_point, focal_vector, u8(tx_rx_orientation)));
    301 	const f32 z_delta_squared  = xdc_world_point.z * xdc_world_point.z;
    302 	const f32 f_number_over_z  = abs(FNumber / xdc_world_point.z);
    303 	const f32 apodization_test = 0.25f / (f_number_over_z * f_number_over_z);
    304 
    305 	const f32 rx_world_point   = xdc_world_point[s32(!rx_cols)];
    306 	const f32 tx_world_point   = xdc_world_point[s32(rx_cols)];
    307 	const f32 rx_pitch         = xdc_element_pitch[s32(!rx_cols)];
    308 	const f32 tx_pitch         = xdc_element_pitch[s32(rx_cols)];
    309 
    310 	RESULT_TYPE result = RESULT_TYPE(0);
    311 	for (f32 chunk_channel = 0.f; chunk_channel < f32(batch_channel_count()); chunk_channel += 1.f) {
    312 		f32 rx_channel  = f32(channel_offset) + chunk_channel;
    313 
    314 		f32 element_receive_delta_squared = rx_world_point - rx_channel * rx_pitch;
    315 		element_receive_delta_squared *= element_receive_delta_squared;
    316 
    317 		u64 rf_pointer = rf_data_pointer(u32(chunk_channel), u32(Sparse));
    318 
    319 		for (f32 transmit = f32(Sparse); transmit < f32(AcquisitionCount); transmit += 1.f) {
    320 			f32 tx_channel = Sparse ? f32(S16(HeapBase + SparseElements - 2 * u32(Sparse)).x[s32(transmit)]) : transmit;
    321 
    322 			f32 element_transmit_delta_squared = tx_world_point - tx_channel * tx_pitch;
    323 			element_transmit_delta_squared *= element_transmit_delta_squared;
    324 
    325 			f32 element_delta_squared = element_transmit_delta_squared + element_receive_delta_squared;
    326 			if (element_delta_squared < apodization_test) {
    327 				/* NOTE: tribal knowledge */
    328 				float apodization = transmit == 0 ? inversesqrt(float(AcquisitionCount)) : 1.0f;
    329 				apodization *= apodize(f_number_over_z * sqrt(element_delta_squared));
    330 
    331 				float index = transmit_index + sqrt(z_delta_squared + element_delta_squared) * SamplingFrequency / SpeedOfSound;
    332 				SAMPLE_TYPE value = apodization * sample_rf(rf_pointer, index);
    333 				result += RESULT_STORE(value);
    334 			}
    335 
    336 			rf_pointer += AcquisitionByteStride;
    337 		}
    338 	}
    339 	return result;
    340 }
    341 
    342 RESULT_TYPE FORCES(const vec3 world_point)
    343 {
    344 	RESULT_TYPE result = RESULT_TYPE(0);
    345 
    346 	const mat4 xdc_transform = transducer_transform(0);
    347 	const u32  rx_axis       = u32(RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows);
    348 	const f32  element_pitch = xdc_element_pitch[rx_axis];
    349 	// TODO(rnp): the sign of the origin offset might be flipped
    350 	const f32  origin_offset = focus_depth * tan(radians(steering_angle));
    351 
    352 	vec3 xdc_world_point       = (xdc_transform * vec4(world_point, 1)).xyz;
    353 	f32  lateral_coord         = xdc_world_point[rx_axis];
    354 	f32  axial_coord           = xdc_world_point.z;
    355 	f32  tx_out_of_plane_delta = world_point[rx_axis ^ 1] + origin_offset;
    356 	f32  z_delta_squared       = axial_coord * axial_coord;
    357 	f32  transmit_oopz_squared = tx_out_of_plane_delta * tx_out_of_plane_delta + z_delta_squared;
    358 
    359 	const u32  AcquisitionChunkSize  = 16;
    360 	const bool AcquisitionChunkSafe  = ((AcquisitionCount + u32(Sparse)) % AcquisitionChunkSize) == 0;
    361 	const u32  AcquisitionChunkCount = (AcquisitionCount + (AcquisitionChunkSize - 1)) / AcquisitionChunkSize;
    362 	for (u32 acquisition = u32(Sparse); acquisition < AcquisitionCount; acquisition += AcquisitionChunkSize) {
    363 		f32 transmit_indices[AcquisitionChunkSize];
    364 		for (u32 transmit = 0; transmit < AcquisitionChunkSize; transmit++) {
    365 			u32 index = acquisition + transmit;
    366 			if (!AcquisitionChunkSafe && index >= AcquisitionCount)
    367 				break;
    368 			f32 tx_channel = Sparse ? f32(S16(HeapBase + SparseElements - 2 * u32(Sparse)).x[index]) : f32(index);
    369 			f32 transmit_delta = lateral_coord - element_pitch * tx_channel;
    370 			transmit_indices[transmit] = sample_index(sqrt(transmit_oopz_squared + transmit_delta * transmit_delta));
    371 		}
    372 
    373 		for (u32 channel = 0; channel < batch_channel_count(); channel += 1u) {
    374 			f32 rx_channel    = f32(channel_offset + channel);
    375 			f32 receive_delta = lateral_coord - rx_channel * element_pitch;
    376 			f32 a_arg         = abs(FNumber * receive_delta / axial_coord);
    377 
    378 			if (a_arg < 0.5f) {
    379 				u64 rf_pointer    = rf_data_pointer(channel, acquisition);
    380 				f32 receive_index = sqrt(receive_delta * receive_delta + z_delta_squared) * SamplingFrequency / SpeedOfSound;
    381 				f32 apodization   = apodize(a_arg);
    382 				for (u32 transmit = 0; transmit < AcquisitionChunkSize; transmit += 1u, rf_pointer += AcquisitionByteStride) {
    383 					if (!AcquisitionChunkSafe && (acquisition + transmit >= AcquisitionCount))
    384 						break;
    385 
    386 					SAMPLE_TYPE value = apodization * sample_rf(rf_pointer, receive_index + transmit_indices[transmit]);
    387 					result += RESULT_STORE(value);
    388 				}
    389 			}
    390 		}
    391 	}
    392 	return result;
    393 }
    394 
    395 RESULT_TYPE READI_FORCES(const vec3 world_point)
    396 {
    397 	RESULT_TYPE result = RESULT_TYPE(0);
    398 
    399 	const mat4 xdc_transform = transducer_transform(0);
    400 	const u32  rx_axis       = u32(RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows);
    401 	const f32  element_pitch = xdc_element_pitch[rx_axis];
    402 	// TODO(rnp): the sign of the origin offset might be flipped
    403 	const f32  origin_offset = focus_depth * tan(radians(steering_angle));
    404 
    405 	vec3 xdc_world_point       = (xdc_transform * vec4(world_point, 1)).xyz;
    406 	f32  lateral_coord         = xdc_world_point[rx_axis];
    407 	f32  axial_coord           = xdc_world_point.z;
    408 	f32  tx_out_of_plane_delta = world_point[rx_axis ^ 1] + origin_offset;
    409 	f32  z_delta_squared       = axial_coord * axial_coord;
    410 	f32  transmit_oopz_squared = tx_out_of_plane_delta * tx_out_of_plane_delta + z_delta_squared;
    411 
    412 	// NOTE(tkh): The row we use matches the acquisition group, the column is the element group we are beamforming.
    413 	s32 hadamard_offset = s32(readi_group) * s32(ReadiGroupCount);
    414 
    415 	for (f32 chunk_channel = 0; chunk_channel < f32(batch_channel_count()); chunk_channel += 1.f) {
    416 		f32 rx_channel    = f32(channel_offset) + chunk_channel;
    417 		f32 receive_delta = lateral_coord - rx_channel * element_pitch;
    418 		f32 a_arg         = abs(FNumber * receive_delta / axial_coord);
    419 
    420 		if (a_arg < 0.5f) {
    421 
    422 			f32 receive_index = sample_index(sqrt(receive_delta * receive_delta + z_delta_squared));
    423 			f32 apodization   = apodize(a_arg);
    424 
    425 			// NOTE(tkh): Iterating over groups of tx elements, each group is AcquisitionCount
    426 			// sequential elements. The first element in each group is beamformed using the first
    427 			// acquisition, the second element in each group is beamformed using the second acquisition, etc.
    428 			for (s32 tx_group = 0; tx_group < s32(ReadiGroupCount); tx_group++) {
    429 				f32 group_apodization = apodization * F16(HeapBase + Hadamard).x[hadamard_offset + tx_group];
    430 
    431 				u64 rf_pointer = rf_data_pointer(u32(chunk_channel), 0);
    432 				for (f32 tx_event = 0; tx_event < f32(AcquisitionCount); tx_event += 1.f, rf_pointer += AcquisitionByteStride) {
    433 					f32 tx_element     = f32(tx_group) * f32(AcquisitionCount) + tx_event;
    434 					f32 transmit_delta = lateral_coord - element_pitch * tx_element;
    435 					f32 transmit_index = sqrt(transmit_oopz_squared + transmit_delta * transmit_delta) * SamplingFrequency / SpeedOfSound;
    436 
    437 					SAMPLE_TYPE value = group_apodization * sample_rf(rf_pointer, receive_index + transmit_index);
    438 					result += RESULT_STORE(value);
    439 				}
    440 			}
    441 		}
    442 	}
    443 	return result;
    444 }
    445 
    446 void main()
    447 {
    448 	uvec3 out_voxel = gl_GlobalInvocationID;
    449 	if (!all(lessThan(out_voxel, uvec3(OutputSizeX, OutputSizeY, OutputSizeZ))))
    450 		return;
    451 
    452 	vec3 image_points = vec3(OutputSizeX, OutputSizeY, OutputSizeZ) - 1.0f;
    453 	vec3 point        = vec3(out_voxel) / max(vec3(1.0f), image_points);
    454 	vec3 world_point  = (voxel_transform * vec4(point, 1)).xyz;
    455 
    456 	uint32_t out_index = output_index(out_voxel.x, out_voxel.y, out_voxel.z);
    457 
    458 	RESULT_TYPE sum = RESULT_TYPE(0);
    459 	switch (AcquisitionKind) {
    460 	case AcquisitionKind_FORCES:
    461 	case AcquisitionKind_UFORCES:
    462 	{
    463 		sum = ReadiGroupCount > 1 ? READI_FORCES(world_point)
    464 		                          : FORCES(world_point);
    465 	}break;
    466 	case AcquisitionKind_HERCULES:
    467 	case AcquisitionKind_UHERCULES:
    468 	case AcquisitionKind_HERO_PA:
    469 	{
    470 		sum = HERCULES(world_point);
    471 	}break;
    472 	case AcquisitionKind_Flash:
    473 	case AcquisitionKind_RCA_TPW:
    474 	case AcquisitionKind_RCA_VLS:
    475 	{
    476 		sum = RCA(world_point);
    477 	}break;
    478 	}
    479 
    480 	#if CoherencyWeighting
    481 	IncoherentOutput(HeapBase + IncoherentFrame).x[out_index] += RESULT_INCOHERENT_CAST(sum);
    482 	#endif
    483 
    484 	Output(output_frame).x[out_index] += RESULT_COHERENT_CAST(sum);
    485 }