main_w32.c (16529B)
1 /* See LICENSE for license details. */ 2 #include "compiler.h" 3 4 // NOTE(rnp): for test compilations on linux (we don't use headers from windows) */ 5 #if OS_LINUX 6 #undef OS_WINDOWS 7 #undef OS_LINUX 8 #undef __declspec 9 #undef __stdcall 10 #define OS_WINDOWS 1 11 #define OS_LINUX 0 12 #define __declspec(x) 13 #define __stdcall 14 #define _WIN32 15 #endif 16 17 #if !OS_WINDOWS 18 #error This file is only meant to be compiled for Win32 19 #endif 20 21 #ifdef BEAMFORMER_DEBUG 22 #define BEAMFORMER_IMPORT __declspec(dllexport) 23 #else 24 #define BEAMFORMER_IMPORT static 25 #define BEAMFORMER_EXPORT static 26 #endif 27 28 #include "beamformer.c" 29 #include "os_win32.c" 30 31 typedef struct { 32 u32 reserved1; 33 u32 reserved2; 34 u64 Reserved3[2]; 35 u32 reserved4; 36 u32 reserved5; 37 } w32_synchronization_barrier; 38 39 W32(u64) CreateThread(iptr, uz, iptr, iptr, u32, u32 *); 40 W32(b32) EnterSynchronizationBarrier(w32_synchronization_barrier *, u32); 41 W32(b32) FreeLibrary(u64); 42 W32(void *) GetModuleHandleA(const c8 *); 43 W32(void *) GetProcAddress(u64, const c8 *); 44 W32(b32) InitializeSynchronizationBarrier(w32_synchronization_barrier *, i32, i32); 45 W32(void *) LoadLibraryA(const c8 *); 46 W32(i32) SetThreadDescription(u64, u16 *); 47 48 #define OS_SHARED_MEMORY_SIZE GB(2) 49 50 #define OS_DEBUG_LIB_NAME ".\\beamformer.dll" 51 #define OS_DEBUG_LIB_TEMP_NAME ".\\beamformer_temp.dll" 52 53 #define OS_CUDA_LIB_NAME "external\\cuda_toolkit.dll" 54 #define OS_CUDA_LIB_TEMP_NAME "external\\cuda_toolkit_temp.dll" 55 56 #define OS_RENDERDOC_SONAME "renderdoc.dll" 57 58 #define OS_VULKAN_SONAME_LIST \ 59 X("vulkan-1.dll") \ 60 61 enum {OSW32_FileWatchDirectoryBufferSize = KB(4)}; 62 63 typedef struct OSW32Entity OSW32Entity; 64 typedef struct { 65 void *handle; 66 OSW32Entity *prev, *next; 67 } OSW32Window; 68 69 typedef enum { 70 OSW32FileWatchKind_Platform, 71 OSW32FileWatchKind_User, 72 } OSW32FileWatchKind; 73 74 typedef struct OSW32FileWatchDirectory OSW32FileWatchDirectory; 75 typedef struct OSW32FileWatch OSW32FileWatch; 76 struct OSW32FileWatch { 77 OSW32FileWatchKind kind; 78 u64 hash; 79 u64 update_time; 80 void *user_context; 81 82 OSW32FileWatchDirectory *parent; 83 OSW32FileWatch *prev, *next; 84 }; 85 86 struct OSW32FileWatchDirectory { 87 u64 hash; 88 i64 handle; 89 str8 name; 90 91 OSW32FileWatch *first_child; 92 OSW32FileWatch *last_child; 93 OSW32FileWatchDirectory *prev, *next; 94 95 w32_overlapped overlapped; 96 w32_io_completion_event event; 97 98 void *buffer; 99 }; 100 101 typedef enum { 102 OSW32EntityKind_Window, 103 OSW32EntityKind_FileWatch, 104 OSW32EntityKind_FileWatchDirectory, 105 } OSW32EntityKind; 106 107 struct OSW32Entity { 108 OSW32EntityKind kind; 109 union { 110 OSW32FileWatch file_watch; 111 OSW32FileWatchDirectory file_watch_directory; 112 OSW32Window window; 113 } as; 114 OSW32Entity *next; 115 }; 116 117 typedef struct { 118 Arena arena; 119 i32 arena_lock; 120 i64 error_handle; 121 i64 io_completion_handle; 122 123 BeamformerInput *input; 124 125 struct { 126 OSW32FileWatchDirectory *first; 127 OSW32FileWatchDirectory *last; 128 } file_watch_directories; 129 130 struct { 131 OSW32Entity *first; 132 OSW32Entity *last; 133 } windows; 134 135 OSW32Entity *entity_freelist; 136 137 OSSystemInfo system_info; 138 } OSW32_Context; 139 global OSW32_Context os_w32_context; 140 141 function OSW32Entity * 142 os_entity_allocate(OSW32EntityKind kind) 143 { 144 OSW32Entity *result = 0; 145 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 146 { 147 result = SLLPopFreelist(os_w32_context.entity_freelist); 148 if (!result) result = push_struct_no_zero(&os_w32_context.arena, OSW32Entity); 149 } 150 151 zero_struct(result); 152 result->kind = kind; 153 return result; 154 } 155 156 BEAMFORMER_IMPORT OSSystemInfo * 157 os_system_info(void) 158 { 159 return &os_w32_context.system_info; 160 } 161 162 BEAMFORMER_IMPORT OSThread 163 os_create_thread(const char *name, void *user_context, os_thread_entry_point_fn *fn) 164 { 165 OSThread result = {(u64)CreateThread(0, 0, (iptr)fn, (iptr)user_context, 0, 0)}; 166 if (result.value[0]) { 167 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 168 { 169 Arena arena = os_w32_context.arena; 170 SetThreadDescription(result.value[0], str16_from_str8(&arena, str8_from_c_str((c8 *)name)).data); 171 } 172 } else { 173 result.value[0] = OSInvalidHandleValue; 174 } 175 return result; 176 } 177 178 BEAMFORMER_IMPORT OSBarrier 179 os_barrier_alloc(u32 count) 180 { 181 OSBarrier result = {0}; 182 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 183 { 184 w32_synchronization_barrier *barrier = push_struct(&os_w32_context.arena, w32_synchronization_barrier); 185 InitializeSynchronizationBarrier(barrier, (i32)count, -1); 186 result.value[0] = (u64)barrier; 187 } 188 return result; 189 } 190 191 BEAMFORMER_IMPORT void 192 os_barrier_enter(OSBarrier barrier) 193 { 194 w32_synchronization_barrier *b = (w32_synchronization_barrier *)barrier.value[0]; 195 if (b) EnterSynchronizationBarrier(b, 0); 196 } 197 198 BEAMFORMER_IMPORT void 199 os_console_log(u8 *data, i64 length) 200 { 201 os_write_file(os_w32_context.error_handle, data, length); 202 } 203 204 BEAMFORMER_IMPORT void 205 os_fatal(u8 *data, i64 length) 206 { 207 os_write_file(os_w32_context.error_handle, data, length); 208 os_exit(1); 209 unreachable(); 210 } 211 212 BEAMFORMER_IMPORT void * 213 os_lookup_symbol(OSLibrary library, const char *symbol) 214 { 215 void *result = 0; 216 if ValidHandle(library) result = GetProcAddress(library.value[0], symbol); 217 return result; 218 } 219 220 function void * 221 allocate_shared_memory(char *name, iz requested_capacity, u64 *capacity) 222 { 223 u64 rounded_capacity = round_up_to(requested_capacity, os_w32_context.system_info.page_size); 224 void *result = 0; 225 iptr h = CreateFileMappingA(-1, 0, PAGE_READWRITE, (rounded_capacity >> 32u), 226 (rounded_capacity & 0xFFFFFFFFul), name); 227 if (h != INVALID_FILE) { 228 result = MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, rounded_capacity); 229 if (result) *capacity = rounded_capacity; 230 } 231 return result; 232 } 233 234 function OSW32FileWatchDirectory * 235 os_lookup_file_watch_directory(u64 hash) 236 { 237 OSW32FileWatchDirectory *result = 0; 238 for (OSW32FileWatchDirectory *fwd = os_w32_context.file_watch_directories.first; fwd; fwd = fwd->next) { 239 if (fwd->hash == hash) { 240 result = fwd; 241 break; 242 } 243 } 244 return result; 245 } 246 247 function void 248 os_w32_add_file_watch(str8 path, void *user_context, OSW32FileWatchKind kind) 249 { 250 str8 directory = path; 251 directory.length = str8_scan_backwards(path, '\\'); 252 assert(directory.length > 0); 253 254 u64 hash = u64_hash_from_str8(directory); 255 OSW32FileWatchDirectory *dir = os_lookup_file_watch_directory(hash); 256 if (!dir) { 257 assert(path.data[directory.length] == '\\'); 258 259 OSW32Entity *fwd = os_entity_allocate(OSW32EntityKind_FileWatchDirectory); 260 dir = &fwd->as.file_watch_directory; 261 DLLInsert(0, os_w32_context.file_watch_directories.first, 262 os_w32_context.file_watch_directories.last, dir, next, prev); 263 264 dir->hash = hash; 265 dir->name = push_str8(&os_w32_context.arena, directory); 266 dir->handle = CreateFileA((c8 *)dir->name.data, GENERIC_READ, FILE_SHARE_READ, 0, 267 OPEN_EXISTING, 268 FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, 0); 269 270 dir->event.tag = W32IOEvent_FileWatch; 271 dir->event.context = (iptr)dir; 272 CreateIoCompletionPort(dir->handle, os_w32_context.io_completion_handle, (uptr)&dir->event, 0); 273 274 dir->buffer = arena_alloc(&os_w32_context.arena, .size = OSW32_FileWatchDirectoryBufferSize); 275 ReadDirectoryChangesW(dir->handle, dir->buffer, OSW32_FileWatchDirectoryBufferSize, 0, 276 FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &dir->overlapped, 0); 277 } 278 279 OSW32Entity *fwe = os_entity_allocate(OSW32EntityKind_FileWatch); 280 OSW32FileWatch *fw = &fwe->as.file_watch; 281 DLLInsert(0, dir->first_child, dir->last_child, fw, next, prev); 282 fw->user_context = user_context; 283 fw->hash = u64_hash_from_str8(str8_cut_head(path, dir->name.length + 1)); 284 fw->kind = kind; 285 fw->parent = dir; 286 } 287 288 BEAMFORMER_IMPORT void 289 os_add_file_watch(const char *path, int64_t path_length, void *user_context) 290 { 291 str8 path_str = {.data = (u8 *)path, .length = path_length}; 292 os_w32_add_file_watch(path_str, user_context, OSW32FileWatchKind_User); 293 } 294 295 function void 296 os_window_resize_callback(void *window, i32 width, i32 height) 297 { 298 OSWindow event_window = {0}; 299 for (OSW32Entity *we = os_w32_context.windows.first; we; we = we->as.window.next) { 300 if (we->as.window.handle == window) { 301 event_window.value[0] = (u64)we; 302 break; 303 } 304 } 305 306 os_push_input_event(beamformer_input, (BeamformerInputEvent){ 307 .kind = BeamformerInputEventKind_WindowResize, 308 .window_resize = { 309 .width = (u32)width, .height = (u32)height, 310 .window = event_window, 311 }, 312 }); 313 314 raylib_window_resize(window, width, height); 315 } 316 317 BEAMFORMER_IMPORT OSWindow 318 os_window_create(u8 *title, i64 title_length, i32 width, i32 height) 319 { 320 OSW32Entity *we = os_entity_allocate(OSW32EntityKind_Window); 321 OSWindow result = {(u64)we}; 322 DLLInsert(0, os_w32_context.windows.first, os_w32_context.windows.last, we, as.window.next, as.window.prev); 323 324 SetConfigFlags(FLAG_VSYNC_HINT|FLAG_WINDOW_ALWAYS_RUN); 325 326 str8 name = {.data = title, .length = title_length}; 327 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 328 { 329 Arena scratch = os_w32_context.arena; 330 name.length = Min(name.length, arena_capacity(&scratch, u8) - 1); 331 str8 title_string = push_str8(&scratch, name); 332 InitWindow(width, height, (char *)title_string.data); 333 } 334 335 we->as.window.handle = GetPlatformWindowHandle(); 336 os_window_equip_common(os_w32_context.input, we->as.window.handle); 337 raylib_window_resize = glfwSetWindowSizeCallback(we->as.window.handle, os_window_resize_callback); 338 339 /* NOTE: do this after initing so that the window starts out floating in tiling wm */ 340 SetWindowState(FLAG_WINDOW_RESIZABLE); 341 SetWindowMinSize(320, 240); 342 343 return result; 344 } 345 346 #if BEAMFORMER_RENDERDOC_HOOKS 347 function OSLibrary 348 get_module(char *name) 349 { 350 OSLibrary result = {(u64)GetModuleHandleA(name)}; 351 if (result.value[0] == 0) result.value[0] = OSInvalidHandleValue; 352 return result; 353 } 354 #endif 355 356 function OSLibrary 357 load_library(char *name, char *temp_name) 358 { 359 if (temp_name && os_copy_file(name, temp_name)) 360 name = temp_name; 361 362 OSLibrary result = {(u64)LoadLibraryA(name)}; 363 if (result.value[0] == 0) result.value[0] = OSInvalidHandleValue; 364 365 if (temp_name) DeleteFileA(temp_name); 366 367 return result; 368 } 369 370 #if BEAMFORMER_DEBUG 371 function void 372 debug_library_reload(BeamformerInput *input) 373 { 374 local_persist OSLibrary beamformer_library_handle = {OSInvalidHandleValue}; 375 376 if ValidHandle(beamformer_library_handle) { 377 beamformer_debug_hot_release(input); 378 FreeLibrary(beamformer_library_handle.value[0]); 379 beamformer_library_handle = (OSLibrary){OSInvalidHandleValue}; 380 } 381 382 OSLibrary new_handle = load_library(OS_DEBUG_LIB_NAME, OS_DEBUG_LIB_TEMP_NAME); 383 if (InvalidHandle(beamformer_library_handle) && InvalidHandle(new_handle)) 384 fatal(s8("[os] failed to load: " OS_DEBUG_LIB_NAME "\n")); 385 386 if ValidHandle(new_handle) { 387 beamformer_debug_hot_reload(new_handle, input); 388 beamformer_library_handle = new_handle; 389 } 390 } 391 #else 392 #define debug_library_reload(a) (void)(a) 393 #endif /* BEAMFORMER_DEBUG */ 394 395 function void 396 load_platform_libraries(BeamformerInput *input) 397 { 398 #if BEAMFORMER_DEBUG 399 debug_library_reload(input); 400 os_w32_add_file_watch(str8(OS_DEBUG_LIB_NAME), (void *)BeamformerInputEventKind_ExecutableReload, 401 OSW32FileWatchKind_Platform); 402 #endif 403 404 input->vulkan_library_handle = (OSLibrary){OSInvalidHandleValue}; 405 #define X(name) \ 406 if InvalidHandle(input->vulkan_library_handle) \ 407 input->vulkan_library_handle = load_library(name, 0); 408 OS_VULKAN_SONAME_LIST 409 #undef X 410 411 if InvalidHandle(input->vulkan_library_handle) 412 fatal(s8("[os] fatal error: failed to find valid vulkan library\n")); 413 414 input->cuda_library_handle = load_library(OS_CUDA_LIB_NAME, OS_CUDA_LIB_TEMP_NAME); 415 416 #if BEAMFORMER_RENDERDOC_HOOKS 417 local_persist OSLibrary renderdoc_handle = {OSInvalidHandleValue}; 418 renderdoc_handle = get_module(OS_RENDERDOC_SONAME); 419 load_renderdoc_functions(input, renderdoc_handle); 420 #endif 421 } 422 423 function void 424 dispatch_file_watch(BeamformerInput *input, Arena arena, u64 current_time, OSW32FileWatchDirectory *fw_dir) 425 { 426 TempArena save_point = {0}; 427 i64 offset = 0; 428 429 w32_file_notify_info *fni = (w32_file_notify_info *)fw_dir->buffer; 430 do { 431 end_temp_arena(save_point); 432 save_point = begin_temp_arena(&arena); 433 434 Stream e = {.data = arena_commit(&arena, KB(1)), .cap = KB(1)}; 435 436 if (fni->action != FILE_ACTION_MODIFIED) { 437 stream_append_s8(&e, s8("[os] unknown file watch event: ")); 438 stream_append_u64(&e, fni->action); 439 stream_append_byte(&e, '\n'); 440 os_write_file(os_w32_context.error_handle, e.data, e.widx); 441 stream_reset(&e, 0); 442 } 443 444 str8 file_name = str8_from_str16(&arena, (str16){.data = fni->filename, .length = fni->filename_size / 2}); 445 u64 hash = u64_hash_from_str8(file_name); 446 for (OSW32FileWatch *fw = fw_dir->first_child; fw; fw = fw->next) if (fw->hash == hash) { 447 // NOTE(rnp): avoid multiple updates in a single frame 448 if (fw->update_time < current_time) { 449 BeamformerInputEvent input_event = {0}; 450 if (fw->kind == OSW32FileWatchKind_Platform) { 451 assert((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload); 452 if ((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload) 453 debug_library_reload(input); 454 input_event.kind = (u64)fw->user_context; 455 } else { 456 input_event.kind = BeamformerInputEventKind_FileEvent; 457 input_event.file_watch_user_context = fw->user_context; 458 } 459 os_push_input_event(input, input_event); 460 } 461 fw->update_time = current_time; 462 break; 463 } 464 465 offset = fni->next_entry_offset; 466 fni = (w32_file_notify_info *)((u8 *)fni + offset); 467 } while (offset); 468 } 469 470 function void 471 clear_io_queue(BeamformerInput *input, Arena arena) 472 { 473 iptr handle = os_w32_context.io_completion_handle; 474 w32_overlapped *overlapped; 475 u32 bytes_read; 476 uptr user_data; 477 478 u64 current_time = os_timer_count(); 479 480 while (GetQueuedCompletionStatus(handle, &bytes_read, &user_data, &overlapped, 0)) { 481 w32_io_completion_event *event = (w32_io_completion_event *)user_data; 482 switch (event->tag) { 483 case W32IOEvent_FileWatch:{ 484 OSW32FileWatchDirectory *dir = (OSW32FileWatchDirectory *)event->context; 485 dispatch_file_watch(input, arena, current_time, dir); 486 zero_struct(&dir->overlapped); 487 ReadDirectoryChangesW(dir->handle, dir->buffer, OSW32_FileWatchDirectoryBufferSize, 0, 488 FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &dir->overlapped, 0); 489 }break; 490 } 491 } 492 } 493 494 extern i32 495 main(void) 496 { 497 os_w32_context.error_handle = GetStdHandle(STD_ERROR_HANDLE); 498 os_w32_context.io_completion_handle = CreateIoCompletionPort(INVALID_FILE, 0, 0, 0); 499 os_w32_context.system_info.timer_frequency = os_timer_frequency(); 500 os_w32_context.system_info.path_separator_byte = '\\'; 501 { 502 w32_system_info info = {0}; 503 GetSystemInfo(&info); 504 505 os_w32_context.system_info.page_size = info.page_size; 506 os_w32_context.system_info.logical_processor_count = info.number_of_processors; 507 } 508 509 Arena program_memory = os_alloc_arena(MB(16) + MB(2)); 510 os_w32_context.arena = sub_arena(&program_memory, MB(2), os_w32_context.system_info.page_size); 511 512 BeamformerInput *input = push_struct(&program_memory, BeamformerInput); 513 os_w32_context.input = input; 514 input->memory = program_memory.beg; 515 input->memory_size = program_memory.end - program_memory.beg; 516 input->shared_memory = allocate_shared_memory(OS_SHARED_MEMORY_NAME, OS_SHARED_MEMORY_SIZE, 517 &input->shared_memory_size); 518 if (input->shared_memory) { 519 input->shared_memory_name = s8(OS_SHARED_MEMORY_NAME).data; 520 input->shared_memory_name_length = s8(OS_SHARED_MEMORY_NAME).len; 521 } 522 523 os_push_input_event(input, (BeamformerInputEvent){ 524 .kind = BeamformerInputEventKind_ExecutableReload, 525 }); 526 527 load_platform_libraries(input); 528 529 beamformer_init(input); 530 531 while (!WindowShouldClose() && !beamformer_should_close(input)) { 532 os_build_frame_input(input); 533 534 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 535 { 536 clear_io_queue(input, os_w32_context.arena); 537 } 538 539 beamformer_frame_step(input); 540 541 // NOTE(rnp): this must happen at the end of frame to allow the pre loop events through 542 // TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause 543 // glfw to call the input callbacks in during EndDrawing() 544 //input->event_count = 0; 545 } 546 547 beamformer_terminate(input); 548 }