ogl_beamforming

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

ogl_beamformer_lib.c (21750B)


      1 /* See LICENSE for license details. */
      2 #include "../compiler.h"
      3 
      4 #define BEAMFORMER_IMPORT static
      5 
      6 #include "../beamformer.h"
      7 
      8 #include "../util.h"
      9 
     10 #include "../generated/beamformer.meta.c"
     11 #include "../beamformer_parameters.h"
     12 #include "ogl_beamformer_lib_base.h"
     13 
     14 #if OS_LINUX
     15 #include "../os_linux.c"
     16 #elif OS_WINDOWS
     17 #include "../os_win32.c"
     18 
     19 W32(iptr) OpenFileMappingA(u32, b32, c8 *);
     20 
     21 #else
     22 #error Unsupported Platform
     23 #endif
     24 
     25 #include "../util_os.c"
     26 #include "../beamformer_shared_memory.c"
     27 
     28 global struct {
     29 	BeamformerSharedMemory *bp;
     30 	i32                     timeout_ms;
     31 	BeamformerLibErrorKind  last_error;
     32 } g_beamformer_library_context;
     33 
     34 #if OS_LINUX
     35 
     36 function void *
     37 os_open_shared_memory_area(char *name)
     38 {
     39 	void *result = 0;
     40 	i32 fd = shm_open(name, O_RDWR, S_IRUSR|S_IWUSR);
     41 	if (fd > 0) {
     42 		void *new = mmap(0, BEAMFORMER_SHARED_MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
     43 		if (new != MAP_FAILED) result = new;
     44 		close(fd);
     45 	}
     46 	return result;
     47 }
     48 
     49 function void
     50 os_close_shared_memory_area(void *memory)
     51 {
     52 	munmap(memory, BEAMFORMER_SHARED_MEMORY_SIZE);
     53 }
     54 
     55 #elif OS_WINDOWS
     56 
     57 W32(b32) UnmapViewOfFile(void *);
     58 
     59 function b32
     60 os_reserve_region_locks(void)
     61 {
     62 	u8 buffer[1024];
     63 	Stream sb = {.data = buffer, .cap = countof(buffer)};
     64 	stream_append_s8(&sb, s8(OS_SHARED_MEMORY_NAME "_lock_"));
     65 
     66 	i32 start_index    = sb.widx;
     67 	u32 reserved_count = 0;
     68 	for EachElement(os_w32_shared_memory_semaphores, it) {
     69 		stream_reset(&sb, start_index);
     70 		stream_append_u64(&sb, it);
     71 		stream_append_byte(&sb, 0);
     72 		os_w32_shared_memory_semaphores[it] = os_w32_create_semaphore((c8 *)sb.data, 1, 1);
     73 		if InvalidHandle(os_w32_shared_memory_semaphores[it])
     74 			break;
     75 		reserved_count++;
     76 	}
     77 
     78 	b32 result = reserved_count == countof(os_w32_shared_memory_semaphores);
     79 	if (!result) {
     80 		for (u32 i = 0; i < reserved_count; i++)
     81 			CloseHandle(os_w32_shared_memory_semaphores[i].value[0]);
     82 	}
     83 
     84 	return result;
     85 }
     86 
     87 function void *
     88 os_open_shared_memory_area(char *name)
     89 {
     90 	iptr h = OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, name);
     91 	void *result = 0;
     92 	if (h != INVALID_FILE) {
     93 		void *new = MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, BEAMFORMER_SHARED_MEMORY_SIZE);
     94 		if (new && os_reserve_region_locks())
     95 			result = new;
     96 		if (new && !result)
     97 			UnmapViewOfFile(new);
     98 		CloseHandle(h);
     99 	}
    100 	return result;
    101 }
    102 
    103 function void
    104 os_close_shared_memory_area(void *memory)
    105 {
    106 	UnmapViewOfFile(memory);
    107 }
    108 
    109 #endif
    110 
    111 #define lib_error_check(c, e) lib_error_check_(c, BeamformerLibErrorKind_##e)
    112 function b32
    113 lib_error_check_(b32 condition, BeamformerLibErrorKind error_kind)
    114 {
    115 	b32 result = condition;
    116 	if (!result) g_beamformer_library_context.last_error = error_kind;
    117 	assert(result);
    118 	return result;
    119 }
    120 
    121 function b32
    122 check_shared_memory(void)
    123 {
    124 	b32 result = g_beamformer_library_context.bp != 0;
    125 	if unlikely(!g_beamformer_library_context.bp) {
    126 		BeamformerSharedMemory *bp = os_open_shared_memory_area(OS_SHARED_MEMORY_NAME);
    127 		if (lib_error_check(bp != 0, SharedMemory)) {
    128 			result = lib_error_check(bp->version == BEAMFORMER_SHARED_MEMORY_VERSION, VersionMismatch);
    129 			if (result) g_beamformer_library_context.bp = bp;
    130 			else        os_close_shared_memory_area(bp);
    131 		}
    132 	}
    133 
    134 	if likely(g_beamformer_library_context.bp)
    135 		result = lib_error_check(likely(!g_beamformer_library_context.bp->invalid), InvalidAccess);
    136 	return result;
    137 }
    138 
    139 function b32
    140 valid_parameter_block(u32 block)
    141 {
    142 	b32 result = check_shared_memory();
    143 	if (result) {
    144 		result = lib_error_check(block < g_beamformer_library_context.bp->reserved_parameter_blocks,
    145 		                         ParameterBlockUnallocated);
    146 	}
    147 	return result;
    148 }
    149 
    150 function BeamformWork *
    151 try_push_work_queue(void)
    152 {
    153 	BeamformWork *result = beamform_work_queue_push(&g_beamformer_library_context.bp->external_work_queue);
    154 	lib_error_check(result != 0, WorkQueueFull);
    155 	return result;
    156 }
    157 
    158 function b32
    159 lib_try_lock(i32 lock, i32 timeout_ms)
    160 {
    161 	b32 result = beamformer_shared_memory_take_lock(g_beamformer_library_context.bp, lock, (u32)timeout_ms);
    162 	lib_error_check(result, SyncVariable);
    163 	return result;
    164 }
    165 
    166 function void
    167 lib_release_lock(i32 lock)
    168 {
    169 	beamformer_shared_memory_release_lock(g_beamformer_library_context.bp, lock);
    170 }
    171 
    172 u32
    173 beamformer_get_api_version(void)
    174 {
    175 	return BEAMFORMER_SHARED_MEMORY_VERSION;
    176 }
    177 
    178 const char *
    179 beamformer_error_string(BeamformerLibErrorKind kind)
    180 {
    181 	#define X(type, num, string) string,
    182 	local_persist const char *error_string_table[] = {BEAMFORMER_LIB_ERRORS "invalid error kind"};
    183 	#undef X
    184 	return error_string_table[MIN(kind, countof(error_string_table) - 1)];
    185 }
    186 
    187 BeamformerLibErrorKind
    188 beamformer_get_last_error(void)
    189 {
    190 	return g_beamformer_library_context.last_error;
    191 }
    192 
    193 const char *
    194 beamformer_get_last_error_string(void)
    195 {
    196 	return beamformer_error_string(beamformer_get_last_error());
    197 }
    198 
    199 void
    200 beamformer_set_global_timeout(u32 timeout_ms)
    201 {
    202 	g_beamformer_library_context.timeout_ms = timeout_ms;
    203 }
    204 
    205 b32
    206 beamformer_reserve_parameter_blocks(uint32_t count)
    207 {
    208 	b32 result = 0;
    209 	if (check_shared_memory() &&
    210 	    lib_error_check(count <= BeamformerMaxParameterBlockSlots, ParameterBlockOverflow))
    211 	{
    212 		g_beamformer_library_context.bp->reserved_parameter_blocks = count;
    213 		result = 1;
    214 	}
    215 	return result;
    216 }
    217 
    218 function b32
    219 validate_pipeline(i32 *shaders, u32 shader_count, BeamformerDataKind data_kind)
    220 {
    221 	b32 result = lib_error_check(shader_count <= BeamformerMaxComputeShaderStages, ComputeStageOverflow);
    222 	if (result) {
    223 		for (u32 i = 0; i < shader_count; i++)
    224 			result &= BETWEEN(shaders[i], BeamformerShaderKind_ComputeFirst, BeamformerShaderKind_ComputeLast);
    225 		if (!result) {
    226 			g_beamformer_library_context.last_error = BeamformerLibErrorKind_InvalidComputeStage;
    227 		} else if (shaders[0] != BeamformerShaderKind_Demodulate &&
    228 		           shaders[0] != BeamformerShaderKind_Decode)
    229 		{
    230 			g_beamformer_library_context.last_error = BeamformerLibErrorKind_InvalidStartShader;
    231 			result = 0;
    232 		} else if (shaders[0] == BeamformerShaderKind_Demodulate &&
    233 		           !(data_kind == BeamformerDataKind_Int16 || data_kind == BeamformerDataKind_Float32))
    234 		{
    235 			g_beamformer_library_context.last_error = BeamformerLibErrorKind_InvalidDemodulationDataKind;
    236 			result = 0;
    237 		}
    238 	}
    239 	return result;
    240 }
    241 
    242 function b32
    243 validate_simple_parameters(BeamformerSimpleParameters *bp)
    244 {
    245 	b32 result = check_shared_memory();
    246 	if (result) {
    247 		result &= bp->channel_count <= BeamformerMaxChannelCount;
    248 		if (!result)
    249 			g_beamformer_library_context.last_error = BeamformerLibErrorKind_InvalidSimpleParameters;
    250 	}
    251 	return result;
    252 }
    253 
    254 function b32
    255 parameter_block_region_upload(void *data, u32 size, u32 block, BeamformerParameterBlockRegions region_id,
    256                               u32 block_offset, i32 timeout_ms)
    257 {
    258 	i32 lock   = BeamformerSharedMemoryLockKind_Count + (i32)block;
    259 	b32 result = valid_parameter_block(block) && lib_try_lock(lock, timeout_ms);
    260 	if (result) {
    261 		mem_copy((u8 *)beamformer_parameter_block(g_beamformer_library_context.bp, block) + block_offset,
    262 		         data, size);
    263 		mark_parameter_block_region_dirty(g_beamformer_library_context.bp, block, region_id);
    264 		lib_release_lock(lock);
    265 	}
    266 	return result;
    267 }
    268 
    269 b32
    270 beamformer_set_pipeline_stage_parameters_at(u32 stage_index, i32 parameter, u32 block)
    271 {
    272 	u32 offset  = BeamformerParameterBlockRegionOffsets[BeamformerParameterBlockRegion_ComputePipeline];
    273 	offset     += offsetof(BeamformerComputePipeline, parameters);
    274 	offset     += (stage_index % BeamformerMaxComputeShaderStages) * sizeof(BeamformerShaderParameters);
    275 	b32 result  = parameter_block_region_upload(&parameter, sizeof(BeamformerShaderParameters), block,
    276 	                                            BeamformerParameterBlockRegion_ComputePipeline, offset,
    277 	                                            g_beamformer_library_context.timeout_ms);
    278 	return result;
    279 }
    280 
    281 b32
    282 beamformer_set_pipeline_stage_parameters(u32 stage_index, i32 parameter)
    283 {
    284 	b32 result = beamformer_set_pipeline_stage_parameters_at(stage_index, parameter, 0);
    285 	return result;
    286 }
    287 
    288 b32
    289 beamformer_push_pipeline_at(i32 *shaders, u32 shader_count, BeamformerDataKind data_kind, u32 block)
    290 {
    291 	b32 result = 0;
    292 	if (check_shared_memory() && validate_pipeline(shaders, shader_count, data_kind)) {
    293 		i32 lock = BeamformerSharedMemoryLockKind_Count + (i32)block;
    294 		if (valid_parameter_block(block) && lib_try_lock(lock, g_beamformer_library_context.timeout_ms)) {
    295 			BeamformerParameterBlock *b = beamformer_parameter_block(g_beamformer_library_context.bp, block);
    296 			mem_copy(&b->pipeline.shaders, shaders, shader_count * sizeof(*shaders));
    297 			mark_parameter_block_region_dirty(g_beamformer_library_context.bp, block,
    298 			                                  BeamformerParameterBlockRegion_ComputePipeline);
    299 			b->pipeline.shader_count = shader_count;
    300 			b->pipeline.data_kind    = data_kind;
    301 			lib_release_lock(lock);
    302 			result = 1;
    303 		}
    304 	}
    305 	return result;
    306 }
    307 
    308 b32
    309 beamformer_push_pipeline(i32 *shaders, u32 shader_count, BeamformerDataKind data_kind)
    310 {
    311 	b32 result = beamformer_push_pipeline_at(shaders, shader_count, data_kind, 0);
    312 	return result;
    313 }
    314 
    315 function b32
    316 beamformer_create_filter_base(BeamformerFilterParameters params, u8 filter_slot, u8 parameter_block)
    317 {
    318 	b32 result = 0;
    319 	if (check_shared_memory()) {
    320 		BeamformWork *work = try_push_work_queue();
    321 		if (work) {
    322 			BeamformerCreateFilterContext *ctx = &work->create_filter_context;
    323 			work->kind = BeamformerWorkKind_CreateFilter;
    324 			ctx->parameters      = params;
    325 			ctx->filter_slot     = filter_slot     % BeamformerFilterSlots;
    326 			ctx->parameter_block = parameter_block % BeamformerMaxParameterBlockSlots;
    327 			beamform_work_queue_push_commit(&g_beamformer_library_context.bp->external_work_queue);
    328 			result = 1;
    329 		}
    330 	}
    331 	return result;
    332 }
    333 
    334 b32
    335 beamformer_create_filter(BeamformerFilterKind kind, void *filter_parameters, u32 filter_size,
    336                          f32 sampling_frequency, b32 complex, u8 filter_slot, u8 parameter_block)
    337 {
    338 	b32 result = 0;
    339 	if (lib_error_check(kind >= 0 && kind < BeamformerFilterKind_Count, InvalidFilterKind)) {
    340 		BeamformerFilterParameters fp = {0};
    341 		/* NOTE(rnp): any parameter struct works as base offset */
    342 		filter_size = MIN(filter_size, sizeof(fp) - offsetof(BeamformerFilterParameters, kaiser));
    343 		mem_copy(&fp.kaiser, filter_parameters, filter_size);
    344 		fp.kind               = kind;
    345 		fp.complex            = complex != 0;
    346 		fp.sampling_frequency = sampling_frequency;
    347 		result = beamformer_create_filter_base(fp, filter_slot, parameter_block);
    348 	}
    349 	return result;
    350 }
    351 
    352 function void
    353 beamformer_flush_commands(void)
    354 {
    355 	i32 lock = BeamformerSharedMemoryLockKind_DispatchCompute;
    356 	beamformer_shared_memory_take_lock(g_beamformer_library_context.bp, lock, 0);
    357 }
    358 
    359 #define BEAMFORMER_UPLOAD_FNS \
    360 	X(channel_mapping,               i16, 1, ChannelMapping) \
    361 	X(focal_vectors,                 f32, 2, FocalVectors)   \
    362 	X(sparse_elements,               i16, 1, SparseElements) \
    363 	X(transmit_receive_orientations, u8,  1, TransmitReceiveOrientations)
    364 
    365 #define X(name, dtype, elements, region_name) \
    366 b32 beamformer_push_##name ##_at(dtype *data, u32 count, u32 block) { \
    367 	b32 result = 0; \
    368 	if (lib_error_check(count <= countof(((BeamformerParameterBlock *)0)->name), BufferOverflow)) { \
    369 		result = parameter_block_region_upload(data, count * elements * sizeof(dtype), block, \
    370 		                                       BeamformerParameterBlockRegion_##region_name,  \
    371 		                                       offsetof(BeamformerParameterBlock, name),      \
    372 		                                       g_beamformer_library_context.timeout_ms);      \
    373 	} \
    374 	return result; \
    375 }
    376 BEAMFORMER_UPLOAD_FNS
    377 #undef X
    378 
    379 #define X(name, dtype, ...) \
    380 b32 beamformer_push_##name (dtype *data, u32 count) { \
    381 	b32 result = beamformer_push_##name ##_at(data, count, 0); \
    382 	return result; \
    383 }
    384 BEAMFORMER_UPLOAD_FNS
    385 #undef X
    386 
    387 function b32
    388 beamformer_push_data_base(void *data, u32 data_size, i32 timeout_ms, u32 block)
    389 {
    390 	b32 result = 0;
    391 	Arena scratch = beamformer_shared_memory_scratch_arena(g_beamformer_library_context.bp);
    392 	BeamformerParameterBlock *b  = beamformer_parameter_block(g_beamformer_library_context.bp, block);
    393 	BeamformerParameters     *bp = &b->parameters;
    394 	BeamformerDataKind data_kind = b->pipeline.data_kind;
    395 
    396 	u32 size     = bp->acquisition_count * bp->sample_count * bp->channel_count * beamformer_data_kind_byte_size[data_kind];
    397 	u32 raw_size = bp->raw_data_dimensions.x * bp->raw_data_dimensions.y * beamformer_data_kind_byte_size[data_kind];
    398 
    399 	if (lib_error_check(size <= arena_capacity(&scratch, u8), BufferOverflow) &&
    400 	    lib_error_check(size <= data_size && data_size == raw_size, DataSizeMismatch))
    401 	{
    402 		if (lib_try_lock(BeamformerSharedMemoryLockKind_UploadRF, timeout_ms)) {
    403 			if (lib_try_lock(BeamformerSharedMemoryLockKind_ScratchSpace, 0)) {
    404 				u32 channel_count      = bp->channel_count;
    405 				u32 out_channel_stride = beamformer_data_kind_byte_size[data_kind] * bp->sample_count * bp->acquisition_count;
    406 				u32 in_channel_stride  = beamformer_data_kind_byte_size[data_kind] * bp->raw_data_dimensions.x;
    407 
    408 				for (u32 channel = 0; channel < channel_count; channel++) {
    409 					u16 data_channel = (u16)b->channel_mapping[channel];
    410 					u32 out_off = out_channel_stride * channel;
    411 					u32 in_off  = in_channel_stride  * data_channel;
    412 					/* TODO(rnp): it would be better to do non temporal copy here, but we can't ensure
    413 					 * 64 byte boundaries. */
    414 					mem_copy(scratch.beg + out_off, (u8 *)data + in_off, out_channel_stride);
    415 				}
    416 
    417 				lib_release_lock(BeamformerSharedMemoryLockKind_ScratchSpace);
    418 				/* TODO(rnp): need a better way to communicate this */
    419 				u64 rf_block_rf_size = (u64)block << 32ULL | (u64)size;
    420 				atomic_store_u64(&g_beamformer_library_context.bp->rf_block_rf_size, rf_block_rf_size);
    421 				result = 1;
    422 			}
    423 		}
    424 	}
    425 	return result;
    426 }
    427 
    428 b32
    429 beamformer_push_data_with_compute(void *data, u32 data_size, u32 image_plane_tag, u32 parameter_slot)
    430 {
    431 	b32 result = 0;
    432 	if (check_shared_memory()) {
    433 		u32 reserved_blocks = g_beamformer_library_context.bp->reserved_parameter_blocks;
    434 		if (lib_error_check(image_plane_tag < BeamformerViewPlaneTag_Count, InvalidImagePlane) &&
    435 		    lib_error_check(parameter_slot < reserved_blocks, ParameterBlockUnallocated) &&
    436 		    beamformer_push_data_base(data, data_size, g_beamformer_library_context.timeout_ms, parameter_slot))
    437 		{
    438 			BeamformWork *work = try_push_work_queue();
    439 			if (work) {
    440 				work->kind = BeamformerWorkKind_ComputeIndirect;
    441 				work->compute_indirect_context.view_plane      = image_plane_tag;
    442 				work->compute_indirect_context.parameter_block = parameter_slot;
    443 				beamform_work_queue_push_commit(&g_beamformer_library_context.bp->external_work_queue);
    444 				beamformer_flush_commands();
    445 				result = 1;
    446 			}
    447 		}
    448 	}
    449 	return result;
    450 }
    451 
    452 b32
    453 beamformer_push_parameters_at(BeamformerParameters *bp, u32 block)
    454 {
    455 	b32 result = parameter_block_region_upload(bp, sizeof(*bp), block,
    456 	                                           BeamformerParameterBlockRegion_Parameters,
    457 	                                           offsetof(BeamformerParameterBlock, parameters),
    458 	                                           g_beamformer_library_context.timeout_ms);
    459 	return result;
    460 }
    461 
    462 b32
    463 beamformer_push_parameters(BeamformerParameters *bp)
    464 {
    465 	b32 result = beamformer_push_parameters_at(bp, 0);
    466 	return result;
    467 }
    468 
    469 b32
    470 beamformer_push_simple_parameters_at(BeamformerSimpleParameters *bp, u32 block)
    471 {
    472 	b32 result = validate_simple_parameters(bp);
    473 	if (result) {
    474 		alignas(64) v2 focal_vectors[countof(bp->steering_angles)];
    475 		for (u32 i = 0; i < countof(bp->steering_angles); i++)
    476 			focal_vectors[i] = (v2){{bp->steering_angles[i], bp->focal_depths[i]}};
    477 
    478 		result &= beamformer_push_parameters_at((BeamformerParameters *)bp, block);
    479 		result &= beamformer_push_pipeline_at(bp->compute_stages, bp->compute_stages_count, (BeamformerDataKind)bp->data_kind, block);
    480 		result &= beamformer_push_channel_mapping_at(bp->channel_mapping, bp->channel_count, block);
    481 		result &= beamformer_push_focal_vectors_at((f32 *)focal_vectors, countof(focal_vectors), block);
    482 		result &= beamformer_push_transmit_receive_orientations_at(bp->transmit_receive_orientations,
    483 		                                                           bp->acquisition_count, block);
    484 
    485 		if (bp->acquisition_kind == BeamformerAcquisitionKind_UFORCES ||
    486 		    bp->acquisition_kind == BeamformerAcquisitionKind_UHERCULES)
    487 		{
    488 			result &= beamformer_push_sparse_elements_at(bp->sparse_elements, bp->acquisition_count, block);
    489 		}
    490 
    491 		for (u32 stage = 0; stage < bp->compute_stages_count; stage++)
    492 			result &= beamformer_set_pipeline_stage_parameters_at(stage, bp->compute_stage_parameters[stage], block);
    493 	}
    494 	return result;
    495 }
    496 
    497 b32
    498 beamformer_push_simple_parameters(BeamformerSimpleParameters *bp)
    499 {
    500 	b32 result = beamformer_push_simple_parameters_at(bp, 0);
    501 	return result;
    502 }
    503 
    504 b32
    505 beamformer_push_parameters_ui(BeamformerUIParameters *bp)
    506 {
    507 	b32 result = parameter_block_region_upload(bp, sizeof(*bp), 0, BeamformerParameterBlockRegion_Parameters,
    508 	                                           offsetof(BeamformerParameterBlock, parameters_ui),
    509 	                                           g_beamformer_library_context.timeout_ms);
    510 	return result;
    511 }
    512 
    513 b32
    514 beamformer_push_parameters_head(BeamformerParametersHead *bp)
    515 {
    516 	b32 result = parameter_block_region_upload(bp, sizeof(*bp), 0, BeamformerParameterBlockRegion_Parameters,
    517 	                                           offsetof(BeamformerParameterBlock, parameters_head),
    518 	                                           g_beamformer_library_context.timeout_ms);
    519 	return result;
    520 }
    521 
    522 function b32
    523 beamformer_export_buffer(BeamformerExportContext export_context)
    524 {
    525 	BeamformWork *work = try_push_work_queue();
    526 	b32 result = work && lib_try_lock(BeamformerSharedMemoryLockKind_ExportSync, 0);
    527 	if (result) {
    528 		work->export_context = export_context;
    529 		work->kind = BeamformerWorkKind_ExportBuffer;
    530 		work->lock = BeamformerSharedMemoryLockKind_ScratchSpace;
    531 		beamform_work_queue_push_commit(&g_beamformer_library_context.bp->external_work_queue);
    532 	}
    533 	return result;
    534 }
    535 
    536 function b32
    537 beamformer_export(BeamformerExportContext export, void *out, i32 timeout_ms)
    538 {
    539 	b32 result = 0;
    540 	if (beamformer_export_buffer(export)) {
    541 		/* NOTE(rnp): if this fails it just means that the work from push_data hasn't
    542 		 * started yet. This is here to catch the other case where the work started
    543 		 * and finished before we finished queuing the export work item */
    544 		beamformer_flush_commands();
    545 
    546 		if (lib_try_lock(BeamformerSharedMemoryLockKind_ExportSync, timeout_ms)) {
    547 			if (lib_try_lock(BeamformerSharedMemoryLockKind_ScratchSpace, 0)) {
    548 				Arena scratch = beamformer_shared_memory_scratch_arena(g_beamformer_library_context.bp);
    549 				mem_copy(out, scratch.beg, export.size);
    550 				lib_release_lock(BeamformerSharedMemoryLockKind_ScratchSpace);
    551 				result = 1;
    552 			}
    553 			lib_release_lock(BeamformerSharedMemoryLockKind_ExportSync);
    554 		}
    555 	}
    556 	return result;
    557 }
    558 
    559 b32
    560 beamformer_beamform_data(BeamformerSimpleParameters *bp, void *data, uint32_t data_size,
    561                          void *out_data, int32_t timeout_ms)
    562 {
    563 	b32 result = validate_simple_parameters(bp);
    564 	if (result) {
    565 		bp->output_points.E[0] = MAX(1, bp->output_points.E[0]);
    566 		bp->output_points.E[1] = MAX(1, bp->output_points.E[1]);
    567 		bp->output_points.E[2] = MAX(1, bp->output_points.E[2]);
    568 
    569 		beamformer_push_simple_parameters(bp);
    570 
    571 		b32 complex = 0;
    572 		for (u32 stage = 0; stage < bp->compute_stages_count; stage++) {
    573 			BeamformerShaderKind shader = (BeamformerShaderKind)bp->compute_stages[stage];
    574 			complex |= shader == BeamformerShaderKind_Demodulate || shader == BeamformerShaderKind_CudaHilbert;
    575 		}
    576 
    577 		iz output_size = bp->output_points.x * bp->output_points.y * bp->output_points.z * (i32)sizeof(f32);
    578 		if (complex) output_size *= 2;
    579 
    580 		Arena scratch = beamformer_shared_memory_scratch_arena(g_beamformer_library_context.bp);
    581 		if (out_data) result = lib_error_check(output_size <= arena_capacity(&scratch, u8), ExportSpaceOverflow);
    582 
    583 		if (result) {
    584 			result = beamformer_push_data_with_compute(data, data_size, 0, 0);
    585 			if (result && out_data) {
    586 				BeamformerExportContext export;
    587 				export.kind = BeamformerExportKind_BeamformedData;
    588 				export.size = (u32)output_size;
    589 				result = beamformer_export(export, out_data, timeout_ms);
    590 			}
    591 		}
    592 	}
    593 	return result;
    594 }
    595 
    596 b32
    597 beamformer_compute_timings(BeamformerComputeStatsTable *output, i32 timeout_ms)
    598 {
    599 	static_assert(sizeof(*output) <= BEAMFORMER_SHARED_MEMORY_MAX_SCRATCH_SIZE,
    600 	              "timing table size exceeds scratch space");
    601 
    602 	b32 result = 0;
    603 	if (check_shared_memory()) {
    604 		Arena scratch = beamformer_shared_memory_scratch_arena(g_beamformer_library_context.bp);
    605 		if (lib_error_check((iz)sizeof(*output) <= arena_capacity(&scratch, u8), ExportSpaceOverflow)) {
    606 			BeamformerExportContext export;
    607 			export.kind = BeamformerExportKind_Stats;
    608 			export.size = sizeof(*output);
    609 			result = beamformer_export(export, output, timeout_ms);
    610 		}
    611 	}
    612 	return result;
    613 }
    614 
    615 i32
    616 beamformer_live_parameters_get_dirty_flag(void)
    617 {
    618 	i32 result = -1;
    619 	if (check_shared_memory()) {
    620 		u32 flag = ctz_u32(g_beamformer_library_context.bp->live_imaging_dirty_flags);
    621 		if (flag != 32) {
    622 			atomic_and_u32(&g_beamformer_library_context.bp->live_imaging_dirty_flags, ~(1u << flag));
    623 			result = (i32)flag;
    624 		}
    625 	}
    626 	return result;
    627 }
    628 
    629 BeamformerLiveImagingParameters *
    630 beamformer_get_live_parameters(void)
    631 {
    632 	BeamformerLiveImagingParameters *result = 0;
    633 	if (check_shared_memory()) result = &g_beamformer_library_context.bp->live_imaging_parameters;
    634 	return result;
    635 }
    636 
    637 b32
    638 beamformer_set_live_parameters(BeamformerLiveImagingParameters *new)
    639 {
    640 	b32 result = 0;
    641 	if (check_shared_memory()) {
    642 		mem_copy(&g_beamformer_library_context.bp->live_imaging_parameters, new, sizeof(*new));
    643 		store_fence();
    644 		result = 1;
    645 	}
    646 	return result;
    647 }