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 }