ogl_beamforming

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

main_linux.c (14233B)


      1 /* See LICENSE for license details. */
      2 #ifndef BEAMFORMER_DEBUG
      3   #define BEAMFORMER_IMPORT static
      4   #define BEAMFORMER_EXPORT static
      5   #define BASE_EXPORT       static
      6 #endif
      7 
      8 #define BASE_IMPORT static
      9 
     10 #include "base_platform.h"
     11 
     12 #if !OS_LINUX
     13 #error This file is only meant to be compiled for Linux
     14 #endif
     15 
     16 #include "beamformer.c"
     17 #include "base_linux.c"
     18 
     19 #define OS_SHARED_MEMORY_SIZE  GB(2)
     20 
     21 #define OS_DEBUG_LIB_NAME      "./beamformer.so"
     22 #define OS_DEBUG_LIB_TEMP_NAME "./beamformer_temp.so"
     23 
     24 #define OS_CUDA_LIB_NAME       "./external/cuda_toolkit.so"
     25 #define OS_CUDA_LIB_TEMP_NAME  "./external/cuda_toolkit_temp.so"
     26 
     27 #define OS_RENDERDOC_SONAME    "librenderdoc.so"
     28 
     29 #define OS_VULKAN_SONAME_LIST \
     30 	X("libvulkan.so") \
     31 	X("libvulkan.so.1") \
     32 
     33 #include <dlfcn.h>
     34 
     35 typedef struct OSLinuxEntity OSLinuxEntity;
     36 typedef struct {
     37 	void          *handle;
     38 	OSLinuxEntity *prev, *next;
     39 } OSLinuxWindow;
     40 
     41 typedef enum {
     42 	OSLinuxFileWatchKind_Platform,
     43 	OSLinuxFileWatchKind_User,
     44 } OSLinuxFileWatchKind;
     45 
     46 typedef struct OSLinuxFileWatchDirectory OSLinuxFileWatchDirectory;
     47 typedef struct OSLinuxFileWatch OSLinuxFileWatch;
     48 struct OSLinuxFileWatch {
     49 	OSLinuxFileWatchKind  kind;
     50 	u64                   hash;
     51 	u64                   update_time;
     52 	void                 *user_context;
     53 
     54 	OSLinuxFileWatchDirectory *parent;
     55 	OSLinuxFileWatch *prev, *next;
     56 };
     57 
     58 struct OSLinuxFileWatchDirectory {
     59 	u64  hash;
     60 	i64  handle;
     61 	str8 name;
     62 
     63 	OSLinuxFileWatch *first_child;
     64 	OSLinuxFileWatch *last_child;
     65 	OSLinuxFileWatchDirectory *prev, *next;
     66 };
     67 
     68 typedef enum {
     69 	OSLinuxEntityKind_Window,
     70 	OSLinuxEntityKind_FileWatch,
     71 	OSLinuxEntityKind_FileWatchDirectory,
     72 } OSLinuxEntityKind;
     73 
     74 struct OSLinuxEntity {
     75 	OSLinuxEntityKind kind;
     76 	union {
     77 		OSLinuxFileWatch          file_watch;
     78 		OSLinuxFileWatchDirectory file_watch_directory;
     79 		OSLinuxWindow             window;
     80 	} as;
     81 	OSLinuxEntity *next;
     82 };
     83 
     84 typedef struct {
     85 	Arena        *arena;
     86 	i32           arena_lock;
     87 
     88 	i32           inotify_handle;
     89 
     90 	BeamformerInput *input;
     91 
     92 	struct {
     93 		OSLinuxFileWatchDirectory *first;
     94 		OSLinuxFileWatchDirectory *last;
     95 	} file_watch_directories;
     96 
     97 	struct {
     98 		OSLinuxEntity *first;
     99 		OSLinuxEntity *last;
    100 	} windows;
    101 
    102 	OSLinuxEntity *entity_freelist;
    103 } OSLinux_Context;
    104 global OSLinux_Context os_linux_context;
    105 
    106 function OSLinuxEntity *
    107 os_entity_allocate(OSLinuxEntityKind kind)
    108 {
    109 	OSLinuxEntity *result = 0;
    110 	DeferLoop(take_lock(&os_linux_context.arena_lock, -1), release_lock(&os_linux_context.arena_lock))
    111 	{
    112 		result = SLLPopFreelist(os_linux_context.entity_freelist);
    113 		if (!result) result = push_struct_no_zero(os_linux_context.arena, OSLinuxEntity);
    114 	}
    115 
    116 	zero_struct(result);
    117 	result->kind = kind;
    118 	return result;
    119 }
    120 
    121 BEAMFORMER_IMPORT OSThread
    122 os_create_thread(const char *name, void *user_context, os_thread_entry_point_fn *fn)
    123 {
    124 	pthread_t thread;
    125 	pthread_create(&thread, 0, (void *)fn, (void *)user_context);
    126 
    127 	if (name) {
    128 		char buffer[16];
    129 		str8 name_str = str8_from_c_str((char *)name);
    130 		u64  length   = (u64)Clamp(name_str.length, 0, countof(buffer) - 1);
    131 		memory_copy(buffer, (char *)name, length);
    132 		buffer[length] = 0;
    133 		pthread_setname_np(thread, buffer);
    134 	}
    135 
    136 	OSThread result = {(u64)thread};
    137 	return result;
    138 }
    139 
    140 BEAMFORMER_IMPORT OSBarrier
    141 os_barrier_alloc(u32 count)
    142 {
    143 	OSBarrier result = {0};
    144 	DeferLoop(take_lock(&os_linux_context.arena_lock, -1), release_lock(&os_linux_context.arena_lock))
    145 	{
    146 		pthread_barrier_t *barrier = push_struct(os_linux_context.arena, pthread_barrier_t);
    147 		pthread_barrier_init(barrier, 0, count);
    148 		result.value[0] = (u64)barrier;
    149 	}
    150 	return result;
    151 }
    152 
    153 BEAMFORMER_IMPORT void
    154 os_barrier_enter(OSBarrier barrier)
    155 {
    156 	pthread_barrier_t *b = (pthread_barrier_t *)barrier.value[0];
    157 	if (b) pthread_barrier_wait(b);
    158 }
    159 
    160 BEAMFORMER_IMPORT void
    161 os_console_log(u8 *data, i64 length)
    162 {
    163 	os_write_file(STDERR_FILENO, data, length);
    164 }
    165 
    166 BEAMFORMER_IMPORT void
    167 os_fatal(u8 *data, i64 length)
    168 {
    169 	os_write_file(STDERR_FILENO, data, length);
    170 	os_exit(1);
    171 	unreachable();
    172 }
    173 
    174 BEAMFORMER_IMPORT void
    175 os_release_handle(OSHandle h)
    176 {
    177 	if ValidHandle(h)
    178 		close(h.value[0]);
    179 }
    180 
    181 BEAMFORMER_IMPORT void *
    182 os_lookup_symbol(OSLibrary library, const char *symbol)
    183 {
    184 	void *result = 0;
    185 	if ValidHandle(library) result = dlsym((void *)library.value[0], symbol);
    186 	return result;
    187 }
    188 
    189 function void *
    190 allocate_shared_memory(char *name, i64 requested_capacity, u64 *capacity)
    191 {
    192 	u64 rounded_capacity = round_up_to(requested_capacity, ARCH_X64? KB(4) : linux_system_info.page_size);
    193 	void *result = 0;
    194 	i32 fd = shm_open(name, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
    195 	if (fd > 0 && ftruncate(fd, rounded_capacity) != -1) {
    196 		void *new = mmap(0, rounded_capacity, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    197 		if (new != MAP_FAILED) {
    198 			*capacity = rounded_capacity;
    199 			result    = new;
    200 		}
    201 	}
    202 	if (fd > 0) close(fd);
    203 	return result;
    204 }
    205 
    206 function OSLinuxFileWatchDirectory *
    207 os_lookup_file_watch_directory(u64 hash)
    208 {
    209 	OSLinuxFileWatchDirectory *result = 0;
    210 	for (OSLinuxFileWatchDirectory *fwd = os_linux_context.file_watch_directories.first; fwd; fwd = fwd->next) {
    211 		if (fwd->hash == hash) {
    212 			result = fwd;
    213 			break;
    214 		}
    215 	}
    216 	return result;
    217 }
    218 
    219 function void
    220 os_linux_add_file_watch(str8 path, void *user_context, OSLinuxFileWatchKind kind)
    221 {
    222 	str8 directory   = path;
    223 	directory.length = str8_scan_backwards(path, '/');
    224 	assert(directory.length > 0);
    225 
    226 	u64 hash = u64_hash_from_str8(directory);
    227 	OSLinuxFileWatchDirectory *dir = os_lookup_file_watch_directory(hash);
    228 	if (!dir) {
    229 		assert(path.data[directory.length] == '/');
    230 		OSLinuxEntity *fwd = os_entity_allocate(OSLinuxEntityKind_FileWatchDirectory);
    231 		dir = &fwd->as.file_watch_directory;
    232 		DLLInsert(0, os_linux_context.file_watch_directories.first,
    233 		          os_linux_context.file_watch_directories.last, dir, next, prev);
    234 
    235 		dir->hash   = hash;
    236 		dir->name   = push_str8(os_linux_context.arena, directory);
    237 		u32 mask    = IN_MOVED_TO|IN_CLOSE_WRITE;
    238 		dir->handle = inotify_add_watch(os_linux_context.inotify_handle, (c8 *)dir->name.data, mask);
    239 	}
    240 
    241 	OSLinuxEntity    *fwe = os_entity_allocate(OSLinuxEntityKind_FileWatch);
    242 	OSLinuxFileWatch *fw  = &fwe->as.file_watch;
    243 	DLLInsert(0, dir->first_child, dir->last_child, fw, next, prev);
    244 	fw->user_context = user_context;
    245 	fw->hash         = u64_hash_from_str8(str8_cut_head(path, dir->name.length + 1));
    246 	fw->kind         = kind;
    247 	fw->parent       = dir;
    248 }
    249 
    250 BEAMFORMER_IMPORT void
    251 os_add_file_watch(const char *path, int64_t path_length, void *user_context)
    252 {
    253 	str8 path_str = {.data = (u8 *)path, .length = path_length};
    254 	os_linux_add_file_watch(path_str, user_context, OSLinuxFileWatchKind_User);
    255 }
    256 
    257 function void
    258 os_window_resize_callback(void *window, i32 width, i32 height)
    259 {
    260 	OSWindow event_window = {0};
    261 	for (OSLinuxEntity *we = os_linux_context.windows.first; we; we = we->as.window.next) {
    262 		if (we->as.window.handle == window) {
    263 			event_window.value[0] = (u64)we;
    264 			break;
    265 		}
    266 	}
    267 
    268 	os_push_input_event(beamformer_input, (BeamformerInputEvent){
    269 		.kind      = BeamformerInputEventKind_WindowResize,
    270 		.window_resize = {
    271 			.width = (u32)width, .height = (u32)height,
    272 			.window = event_window,
    273 		},
    274 	});
    275 
    276 	raylib_window_resize(window, width, height);
    277 }
    278 
    279 BEAMFORMER_IMPORT OSWindow
    280 os_window_create(u8 *title, i64 title_length, i32 width, i32 height)
    281 {
    282 	OSLinuxEntity *we = os_entity_allocate(OSLinuxEntityKind_Window);
    283 	OSWindow result = {(u64)we};
    284 	DLLInsert(0, os_linux_context.windows.first, os_linux_context.windows.last, we, as.window.next, as.window.prev);
    285 
    286 	SetConfigFlags(FLAG_VSYNC_HINT|FLAG_WINDOW_ALWAYS_RUN);
    287 
    288 	str8 name = {.data = title, .length = title_length};
    289 	Temp scratch;
    290 	DeferLoop(take_lock(&os_linux_context.arena_lock, -1), release_lock(&os_linux_context.arena_lock))
    291 	DeferLoop(scratch = temp_begin(os_linux_context.arena), temp_end(scratch))
    292 	{
    293 		str8 title_string = push_str8(scratch.arena, name);
    294 		InitWindow(width, height, (char *)title_string.data);
    295 	}
    296 
    297 	we->as.window.handle = GetPlatformWindowHandle();
    298 	os_window_equip_common(os_linux_context.input, we->as.window.handle);
    299 	raylib_window_resize = glfwSetWindowSizeCallback(we->as.window.handle, os_window_resize_callback);
    300 
    301 	/* NOTE: do this after initing so that the window starts out floating in tiling wm */
    302 	SetWindowState(FLAG_WINDOW_RESIZABLE);
    303 	SetWindowMinSize(320, 240);
    304 
    305 	return result;
    306 }
    307 
    308 BEAMFORMER_IMPORT u8 *
    309 os_get_clipboard_text(i64 *length)
    310 {
    311 	str8 result = str8_from_c_str((char *)GetClipboardText());
    312 	if (length) *length = result.length;
    313 	return result.data;
    314 }
    315 
    316 BEAMFORMER_IMPORT void
    317 os_set_clipboard_text(u8 *data, i64 length)
    318 {
    319 	Temp scratch;
    320 	DeferLoop(take_lock(&os_linux_context.arena_lock, -1), release_lock(&os_linux_context.arena_lock))
    321 	DeferLoop(scratch = temp_begin(os_linux_context.arena), temp_end(scratch))
    322 	{
    323 		str8 string = push_str8(scratch.arena, (str8){.data = data, .length = length});
    324 		SetClipboardText((char *)string.data);
    325 	}
    326 }
    327 
    328 function OSLibrary
    329 load_library(char *name, char *temp_name, u32 flags)
    330 {
    331 	if (temp_name && os_copy_file(name, temp_name))
    332 		name = temp_name;
    333 
    334 	OSLibrary result = {(u64)dlopen(name, flags)};
    335 	if (result.value[0] == 0) result.value[0] = OSInvalidHandleValue;
    336 
    337 	if (temp_name) unlink(temp_name);
    338 
    339 	return result;
    340 }
    341 
    342 #if BEAMFORMER_DEBUG
    343 function void
    344 debug_library_reload(void *beamformer, BeamformerInput *input)
    345 {
    346 	local_persist OSLibrary beamformer_library_handle = {OSInvalidHandleValue};
    347 
    348 	if ValidHandle(beamformer_library_handle) {
    349 		beamformer_debug_hot_release(beamformer, input);
    350 		dlclose((void *)beamformer_library_handle.value[0]);
    351 		beamformer_library_handle = (OSLibrary){OSInvalidHandleValue};
    352 	}
    353 
    354 	OSLibrary new_handle = load_library(OS_DEBUG_LIB_NAME, OS_DEBUG_LIB_TEMP_NAME, RTLD_NOW|RTLD_LOCAL);
    355 	if (InvalidHandle(beamformer_library_handle) && InvalidHandle(new_handle))
    356 		fatal(str8("[os] failed to load: " OS_DEBUG_LIB_NAME "\n"));
    357 
    358 	if ValidHandle(new_handle) {
    359 		beamformer_debug_hot_reload(new_handle);
    360 		beamformer_library_handle = new_handle;
    361 	}
    362 }
    363 #else
    364 #define debug_library_reload(a, b) (void)(a), (void)(b)
    365 #endif /* BEAMFORMER_DEBUG */
    366 
    367 function void
    368 load_platform_libraries(BeamformerInput *input)
    369 {
    370 	#if BEAMFORMER_DEBUG
    371 		debug_library_reload(0, input);
    372 		os_linux_add_file_watch(str8(OS_DEBUG_LIB_NAME), (void *)BeamformerInputEventKind_ExecutableReload,
    373 		                        OSLinuxFileWatchKind_Platform);
    374 	#endif
    375 
    376 	input->vulkan_library_handle = (OSLibrary){OSInvalidHandleValue};
    377 	#define X(name) \
    378 		if InvalidHandle(input->vulkan_library_handle) \
    379 			input->vulkan_library_handle = load_library(name, 0, RTLD_NOW|RTLD_LOCAL);
    380 	OS_VULKAN_SONAME_LIST
    381 	#undef X
    382 
    383 	if InvalidHandle(input->vulkan_library_handle)
    384 		fatal(str8("[os] fatal error: failed to find valid vulkan library\n"));
    385 
    386 	input->cuda_library_handle = load_library(OS_CUDA_LIB_NAME, OS_CUDA_LIB_TEMP_NAME, RTLD_NOW|RTLD_LOCAL);
    387 
    388 	#if BEAMFORMER_RENDERDOC_HOOKS
    389 	local_persist OSLibrary renderdoc_handle = {OSInvalidHandleValue};
    390 	renderdoc_handle = load_library(OS_RENDERDOC_SONAME, 0, RTLD_NOW|RTLD_LOCAL|RTLD_NOLOAD);
    391 	load_renderdoc_functions(input, renderdoc_handle);
    392 	#endif
    393 }
    394 
    395 function void
    396 dispatch_file_watch_events(void *beamformer, BeamformerInput *input)
    397 {
    398 	local_persist alignas(16) u8 mem[KB(4)];
    399 	struct inotify_event *event;
    400 
    401 	u64 current_time = os_timer_count();
    402 
    403 	i64 rlen;
    404 	while ((rlen = read(os_linux_context.inotify_handle, mem, countof(mem))) > 0) {
    405 		for (u8 *data = mem; data < mem + rlen; data += sizeof(*event) + event->len) {
    406 			event = (struct inotify_event *)data;
    407 			for (OSLinuxFileWatchDirectory *dir = os_linux_context.file_watch_directories.first; dir; dir = dir->next) {
    408 				if (event->wd != dir->handle)
    409 					continue;
    410 
    411 				str8 file = str8_from_c_str(event->name);
    412 				u64  hash = u64_hash_from_str8(file);
    413 				for (OSLinuxFileWatch *fw = dir->first_child; fw; fw = fw->next) if (fw->hash == hash) {
    414 					// NOTE(rnp): avoid multiple updates in a single frame
    415 					if (fw->update_time < current_time) {
    416 						BeamformerInputEvent input_event = {0};
    417 						if (fw->kind == OSLinuxFileWatchKind_Platform) {
    418 							assert((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload);
    419 							if ((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload)
    420 								debug_library_reload(beamformer, input);
    421 							input_event.kind = (u64)fw->user_context;
    422 						} else {
    423 							input_event.kind = BeamformerInputEventKind_FileEvent;
    424 							input_event.file_watch_user_context = fw->user_context;
    425 						}
    426 						os_push_input_event(input, input_event);
    427 					}
    428 					fw->update_time = current_time;
    429 					break;
    430 				}
    431 			}
    432 		}
    433 	}
    434 }
    435 
    436 BASE_IMPORT void
    437 entry_point(i32 argc, char *argv[])
    438 {
    439 	os_linux_context.arena          = arena_create(.name = "Platform Arena");
    440 	os_linux_context.inotify_handle = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
    441 
    442 	BeamformerInput *input = push_struct(os_linux_context.arena, BeamformerInput);
    443 	os_linux_context.input = input;
    444 	input->shared_memory   = allocate_shared_memory(OS_SHARED_MEMORY_NAME, OS_SHARED_MEMORY_SIZE,
    445 	                                                &input->shared_memory_size);
    446 	if (input->shared_memory) {
    447 		input->shared_memory_name        = str8(OS_SHARED_MEMORY_NAME).data;
    448 		input->shared_memory_name_length = str8(OS_SHARED_MEMORY_NAME).length;
    449 	}
    450 
    451 	os_push_input_event(input, (BeamformerInputEvent){
    452 		.kind = BeamformerInputEventKind_ExecutableReload,
    453 	});
    454 
    455 	load_platform_libraries(input);
    456 
    457 	void *beamformer = beamformer_init(input);
    458 
    459 	struct pollfd fds[1] = {{0}};
    460 	fds[0].fd     = os_linux_context.inotify_handle;
    461 	fds[0].events = POLLIN;
    462 
    463 	while (!WindowShouldClose() && !beamformer_should_close(beamformer, input)) {
    464 		os_build_frame_input(input);
    465 
    466 		poll(fds, countof(fds), 0);
    467 		if (fds[0].revents & POLLIN)
    468 			dispatch_file_watch_events(beamformer, input);
    469 
    470 		beamformer_frame_step(beamformer, input);
    471 
    472 		// NOTE(rnp): this must happen at the end of frame to allow the pre loop events through
    473 		// TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause
    474 		// glfw to call the input callbacks in during EndDrawing()
    475 		//input->event_count = 0;
    476 	}
    477 
    478 	beamformer_terminate(beamformer, input);
    479 
    480 	/* NOTE: make sure this will get cleaned up after external
    481 	 * programs release their references */
    482 	shm_unlink(OS_SHARED_MEMORY_NAME);
    483 }