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 }