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 (21498B)


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