main_linux.c (10697B)
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 enum { 33 OSLinux_FileWatchKindPlatform, 34 OSLinux_FileWatchKindUser, 35 } OSLinux_FileWatchKind; 36 37 typedef struct { 38 OSLinux_FileWatchKind kind; 39 u64 hash; 40 u64 update_time; 41 void * user_context; 42 } OSLinux_FileWatch; 43 44 typedef struct { 45 u64 hash; 46 iptr handle; 47 s8 name; 48 49 OSLinux_FileWatch *data; 50 da_count count; 51 da_count capacity; 52 } OSLinux_FileWatchDirectory; 53 DA_STRUCT(OSLinux_FileWatchDirectory, OSLinux_FileWatchDirectory); 54 55 typedef struct { 56 Arena arena; 57 i32 arena_lock; 58 59 i32 inotify_handle; 60 61 OSLinux_FileWatchDirectoryList file_watch_list; 62 63 OSSystemInfo system_info; 64 } OSLinux_Context; 65 global OSLinux_Context os_linux_context; 66 67 BEAMFORMER_IMPORT OSSystemInfo * 68 os_system_info(void) 69 { 70 return &os_linux_context.system_info; 71 } 72 73 BEAMFORMER_IMPORT OSThread 74 os_create_thread(const char *name, void *user_context, os_thread_entry_point_fn *fn) 75 { 76 pthread_t thread; 77 pthread_create(&thread, 0, (void *)fn, (void *)user_context); 78 79 if (name) { 80 char buffer[16]; 81 s8 name_str = c_str_to_s8((char *)name); 82 u64 length = (u64)CLAMP(name_str.len, 0, countof(buffer) - 1); 83 mem_copy(buffer, (char *)name, length); 84 buffer[length] = 0; 85 pthread_setname_np(thread, buffer); 86 } 87 88 OSThread result = {(u64)thread}; 89 return result; 90 } 91 92 BEAMFORMER_IMPORT OSBarrier 93 os_barrier_alloc(u32 count) 94 { 95 OSBarrier result = {0}; 96 DeferLoop(take_lock(&os_linux_context.arena_lock, -1), release_lock(&os_linux_context.arena_lock)) 97 { 98 pthread_barrier_t *barrier = push_struct(&os_linux_context.arena, pthread_barrier_t); 99 pthread_barrier_init(barrier, 0, count); 100 result.value[0] = (u64)barrier; 101 } 102 return result; 103 } 104 105 BEAMFORMER_IMPORT void 106 os_barrier_enter(OSBarrier barrier) 107 { 108 pthread_barrier_t *b = (pthread_barrier_t *)barrier.value[0]; 109 if (b) pthread_barrier_wait(b); 110 } 111 112 BEAMFORMER_IMPORT void 113 os_console_log(u8 *data, i64 length) 114 { 115 os_write_file(STDERR_FILENO, data, length); 116 } 117 118 BEAMFORMER_IMPORT void 119 os_fatal(u8 *data, i64 length) 120 { 121 os_write_file(STDERR_FILENO, data, length); 122 os_exit(1); 123 unreachable(); 124 } 125 126 BEAMFORMER_IMPORT void * 127 os_lookup_symbol(OSLibrary library, const char *symbol) 128 { 129 void *result = 0; 130 if ValidHandle(library) result = dlsym((void *)library.value[0], symbol); 131 return result; 132 } 133 134 function void * 135 allocate_shared_memory(char *name, iz requested_capacity, u64 *capacity) 136 { 137 u64 rounded_capacity = round_up_to(requested_capacity, ARCH_X64? KB(4) : os_linux_context.system_info.page_size); 138 void *result = 0; 139 i32 fd = shm_open(name, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); 140 if (fd > 0 && ftruncate(fd, rounded_capacity) != -1) { 141 void *new = mmap(0, rounded_capacity, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 142 if (new != MAP_FAILED) { 143 *capacity = rounded_capacity; 144 result = new; 145 } 146 } 147 if (fd > 0) close(fd); 148 return result; 149 } 150 151 function OSLinux_FileWatchDirectory * 152 os_lookup_file_watch_directory(OSLinux_FileWatchDirectoryList *ctx, u64 hash) 153 { 154 OSLinux_FileWatchDirectory *result = 0; 155 for (da_count i = 0; !result && i < ctx->count; i++) 156 if (ctx->data[i].hash == hash) 157 result = ctx->data + i; 158 return result; 159 } 160 161 function void 162 os_linux_add_file_watch(s8 path, void *user_context, OSLinux_FileWatchKind kind) 163 { 164 s8 directory = path; 165 directory.len = s8_scan_backwards(path, '/'); 166 assert(directory.len > 0); 167 168 OSLinux_FileWatchDirectoryList *fwctx = &os_linux_context.file_watch_list; 169 170 u64 hash = u64_hash_from_s8(directory); 171 OSLinux_FileWatchDirectory *dir = os_lookup_file_watch_directory(fwctx, hash); 172 if (!dir) { 173 assert(path.data[directory.len] == '/'); 174 dir = da_push(&os_linux_context.arena, fwctx); 175 dir->hash = hash; 176 dir->name = push_s8(&os_linux_context.arena, directory); 177 u32 mask = IN_MOVED_TO|IN_CLOSE_WRITE; 178 dir->handle = inotify_add_watch(os_linux_context.inotify_handle, (c8 *)dir->name.data, mask); 179 } 180 181 OSLinux_FileWatch *fw = da_push(&os_linux_context.arena, dir); 182 fw->user_context = user_context; 183 fw->hash = u64_hash_from_s8(s8_cut_head(path, dir->name.len + 1)); 184 fw->kind = kind; 185 } 186 187 BEAMFORMER_IMPORT void 188 os_add_file_watch(const char *path, int64_t path_length, void *user_context) 189 { 190 s8 path_str = {.data = (u8 *)path, .len = path_length}; 191 os_linux_add_file_watch(path_str, user_context, OSLinux_FileWatchKindUser); 192 } 193 194 function OSLibrary 195 load_library(char *name, char *temp_name, u32 flags) 196 { 197 if (temp_name && os_copy_file(name, temp_name)) 198 name = temp_name; 199 200 OSLibrary result = {(u64)dlopen(name, flags)}; 201 if (result.value[0] == 0) result.value[0] = OSInvalidHandleValue; 202 203 if (temp_name) unlink(temp_name); 204 205 return result; 206 } 207 208 #if BEAMFORMER_DEBUG 209 function void 210 debug_library_reload(BeamformerInput *input) 211 { 212 local_persist OSLibrary beamformer_library_handle = {OSInvalidHandleValue}; 213 214 if ValidHandle(beamformer_library_handle) { 215 beamformer_debug_hot_release(input); 216 dlclose((void *)beamformer_library_handle.value[0]); 217 beamformer_library_handle = (OSLibrary){OSInvalidHandleValue}; 218 } 219 220 OSLibrary new_handle = load_library(OS_DEBUG_LIB_NAME, OS_DEBUG_LIB_TEMP_NAME, RTLD_NOW|RTLD_LOCAL); 221 if (InvalidHandle(beamformer_library_handle) && InvalidHandle(new_handle)) 222 fatal(s8("[os] failed to load: " OS_DEBUG_LIB_NAME "\n")); 223 224 if ValidHandle(new_handle) { 225 beamformer_debug_hot_reload(new_handle, input); 226 beamformer_library_handle = new_handle; 227 } 228 } 229 #endif /* BEAMFORMER_DEBUG */ 230 231 function void 232 load_platform_libraries(BeamformerInput *input) 233 { 234 #if BEAMFORMER_DEBUG 235 debug_library_reload(input); 236 os_linux_add_file_watch(s8(OS_DEBUG_LIB_NAME), (void *)BeamformerInputEventKind_ExecutableReload, 237 OSLinux_FileWatchKindPlatform); 238 #endif 239 240 input->vulkan_library_handle = (OSLibrary){OSInvalidHandleValue}; 241 #define X(name) \ 242 if InvalidHandle(input->vulkan_library_handle) \ 243 input->vulkan_library_handle = load_library(name, 0, RTLD_NOW|RTLD_LOCAL); 244 OS_VULKAN_SONAME_LIST 245 #undef X 246 247 if InvalidHandle(input->vulkan_library_handle) 248 fatal(s8("[os] fatal error: failed to find valid vulkan library\n")); 249 250 input->cuda_library_handle = load_library(OS_CUDA_LIB_NAME, OS_CUDA_LIB_TEMP_NAME, RTLD_NOW|RTLD_LOCAL); 251 252 #if BEAMFORMER_RENDERDOC_HOOKS 253 local_persist OSLibrary renderdoc_handle = {OSInvalidHandleValue}; 254 renderdoc_handle = load_library(OS_RENDERDOC_SONAME, 0, RTLD_NOW|RTLD_LOCAL|RTLD_NOLOAD); 255 load_renderdoc_functions(input, renderdoc_handle); 256 #endif 257 } 258 259 function void 260 dispatch_file_watch_events(BeamformerInput *input) 261 { 262 OSLinux_FileWatchDirectoryList *fwctx = &os_linux_context.file_watch_list; 263 Arena arena = os_linux_context.arena; 264 u8 *mem = arena_alloc(&arena, .size = 4096, .align = 16); 265 struct inotify_event *event; 266 267 u64 current_time = os_timer_count(); 268 269 iz rlen; 270 while ((rlen = read(os_linux_context.inotify_handle, mem, 4096)) > 0) { 271 for (u8 *data = mem; data < mem + rlen; data += sizeof(*event) + event->len) { 272 event = (struct inotify_event *)data; 273 for (da_count i = 0; i < fwctx->count; i++) { 274 OSLinux_FileWatchDirectory *dir = fwctx->data + i; 275 if (event->wd != dir->handle) 276 continue; 277 278 s8 file = c_str_to_s8(event->name); 279 u64 hash = u64_hash_from_s8(file); 280 for (da_count j = 0; j < dir->count; j++) { 281 OSLinux_FileWatch *fw = dir->data + j; 282 if (fw->hash == hash) { 283 // NOTE(rnp): avoid multiple updates in a single frame 284 if (fw->update_time < current_time) { 285 BeamformerInputEvent input_event = {0}; 286 if (fw->kind == OSLinux_FileWatchKindPlatform) { 287 assert((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload); 288 #if BEAMFORMER_DEBUG 289 if ((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload) 290 debug_library_reload(input); 291 #endif 292 input_event.kind = (u64)fw->user_context; 293 } else { 294 input_event.kind = BeamformerInputEventKind_FileEvent; 295 input_event.file_watch_user_context = fw->user_context; 296 } 297 input->event_queue[input->event_count++] = input_event; 298 } 299 fw->update_time = current_time; 300 break; 301 } 302 } 303 } 304 } 305 } 306 } 307 308 extern i32 309 main(void) 310 { 311 os_linux_context.system_info.timer_frequency = os_timer_frequency(); 312 os_linux_context.system_info.logical_processor_count = os_number_of_processors(); 313 os_linux_context.system_info.page_size = ARCH_X64? KB(4) : getauxval(AT_PAGESZ); 314 os_linux_context.system_info.path_separator_byte = '/'; 315 316 Arena program_memory = os_alloc_arena(MB(16) + KB(16)); 317 318 os_linux_context.arena = sub_arena(&program_memory, KB(16), KB(4)); 319 os_linux_context.inotify_handle = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); 320 321 BeamformerInput *input = push_struct(&program_memory, BeamformerInput); 322 input->memory = program_memory.beg; 323 input->memory_size = program_memory.end - program_memory.beg; 324 input->shared_memory = allocate_shared_memory(OS_SHARED_MEMORY_NAME, OS_SHARED_MEMORY_SIZE, 325 &input->shared_memory_size); 326 if (input->shared_memory) { 327 input->shared_memory_name = s8(OS_SHARED_MEMORY_NAME).data; 328 input->shared_memory_name_length = s8(OS_SHARED_MEMORY_NAME).len; 329 } 330 331 input->event_queue[input->event_count++] = (BeamformerInputEvent){ 332 .kind = BeamformerInputEventKind_ExecutableReload, 333 }; 334 335 load_platform_libraries(input); 336 337 beamformer_init(input); 338 339 struct pollfd fds[1] = {{0}}; 340 fds[0].fd = os_linux_context.inotify_handle; 341 fds[0].events = POLLIN; 342 343 while (!WindowShouldClose() && !beamformer_should_close(input)) { 344 os_build_frame_input(input); 345 346 poll(fds, countof(fds), 0); 347 if (fds[0].revents & POLLIN) 348 dispatch_file_watch_events(input); 349 350 beamformer_frame_step(input); 351 352 // NOTE(rnp): this must happen at the end of frame to allow the pre loop events through 353 input->event_count = 0; 354 } 355 356 beamformer_terminate(input); 357 358 /* NOTE: make sure this will get cleaned up after external 359 * programs release their references */ 360 shm_unlink(OS_SHARED_MEMORY_NAME); 361 }