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