main_w32.c (17138B)
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, u64, 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, i64 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 BEAMFORMER_IMPORT u8 * 347 os_get_clipboard_text(i64 *length) 348 { 349 str8 result = str8_from_c_str((char *)GetClipboardText()); 350 if (length) *length = result.length; 351 return result.data; 352 } 353 354 BEAMFORMER_IMPORT void 355 os_set_clipboard_text(u8 *data, i64 length) 356 { 357 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 358 { 359 Arena scratch = os_w32_context.arena; 360 str8 string = {.data = data, .length = Min(length, arena_capacity(&scratch, u8) - 1)}; 361 str8 safe_string = push_str8(&scratch, string); 362 SetClipboardText((char *)safe_string.data); 363 } 364 } 365 366 #if BEAMFORMER_RENDERDOC_HOOKS 367 function OSLibrary 368 get_module(char *name) 369 { 370 OSLibrary result = {(u64)GetModuleHandleA(name)}; 371 if (result.value[0] == 0) result.value[0] = OSInvalidHandleValue; 372 return result; 373 } 374 #endif 375 376 function OSLibrary 377 load_library(char *name, char *temp_name) 378 { 379 if (temp_name && os_copy_file(name, temp_name)) 380 name = temp_name; 381 382 OSLibrary result = {(u64)LoadLibraryA(name)}; 383 if (result.value[0] == 0) result.value[0] = OSInvalidHandleValue; 384 385 if (temp_name) DeleteFileA(temp_name); 386 387 return result; 388 } 389 390 #if BEAMFORMER_DEBUG 391 function void 392 debug_library_reload(BeamformerInput *input) 393 { 394 local_persist OSLibrary beamformer_library_handle = {OSInvalidHandleValue}; 395 396 if ValidHandle(beamformer_library_handle) { 397 beamformer_debug_hot_release(input); 398 FreeLibrary(beamformer_library_handle.value[0]); 399 beamformer_library_handle = (OSLibrary){OSInvalidHandleValue}; 400 } 401 402 OSLibrary new_handle = load_library(OS_DEBUG_LIB_NAME, OS_DEBUG_LIB_TEMP_NAME); 403 if (InvalidHandle(beamformer_library_handle) && InvalidHandle(new_handle)) 404 fatal(str8("[os] failed to load: " OS_DEBUG_LIB_NAME "\n")); 405 406 if ValidHandle(new_handle) { 407 beamformer_debug_hot_reload(new_handle, input); 408 beamformer_library_handle = new_handle; 409 } 410 } 411 #else 412 #define debug_library_reload(a) (void)(a) 413 #endif /* BEAMFORMER_DEBUG */ 414 415 function void 416 load_platform_libraries(BeamformerInput *input) 417 { 418 #if BEAMFORMER_DEBUG 419 debug_library_reload(input); 420 os_w32_add_file_watch(str8(OS_DEBUG_LIB_NAME), (void *)BeamformerInputEventKind_ExecutableReload, 421 OSW32FileWatchKind_Platform); 422 #endif 423 424 input->vulkan_library_handle = (OSLibrary){OSInvalidHandleValue}; 425 #define X(name) \ 426 if InvalidHandle(input->vulkan_library_handle) \ 427 input->vulkan_library_handle = load_library(name, 0); 428 OS_VULKAN_SONAME_LIST 429 #undef X 430 431 if InvalidHandle(input->vulkan_library_handle) 432 fatal(str8("[os] fatal error: failed to find valid vulkan library\n")); 433 434 input->cuda_library_handle = load_library(OS_CUDA_LIB_NAME, OS_CUDA_LIB_TEMP_NAME); 435 436 #if BEAMFORMER_RENDERDOC_HOOKS 437 local_persist OSLibrary renderdoc_handle = {OSInvalidHandleValue}; 438 renderdoc_handle = get_module(OS_RENDERDOC_SONAME); 439 load_renderdoc_functions(input, renderdoc_handle); 440 #endif 441 } 442 443 function void 444 dispatch_file_watch(BeamformerInput *input, Arena arena, u64 current_time, OSW32FileWatchDirectory *fw_dir) 445 { 446 TempArena save_point = {0}; 447 i64 offset = 0; 448 449 w32_file_notify_info *fni = (w32_file_notify_info *)fw_dir->buffer; 450 do { 451 end_temp_arena(save_point); 452 save_point = begin_temp_arena(&arena); 453 454 Stream e = {.data = arena_commit(&arena, KB(1)), .cap = KB(1)}; 455 456 if (fni->action != FILE_ACTION_MODIFIED) { 457 stream_append_str8(&e, str8("[os] unknown file watch event: ")); 458 stream_append_u64(&e, fni->action); 459 stream_append_byte(&e, '\n'); 460 os_write_file(os_w32_context.error_handle, e.data, e.widx); 461 stream_reset(&e, 0); 462 } 463 464 str8 file_name = str8_from_str16(&arena, (str16){.data = fni->filename, .length = fni->filename_size / 2}); 465 u64 hash = u64_hash_from_str8(file_name); 466 for (OSW32FileWatch *fw = fw_dir->first_child; fw; fw = fw->next) if (fw->hash == hash) { 467 // NOTE(rnp): avoid multiple updates in a single frame 468 if (fw->update_time < current_time) { 469 BeamformerInputEvent input_event = {0}; 470 if (fw->kind == OSW32FileWatchKind_Platform) { 471 assert((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload); 472 if ((u64)fw->user_context == BeamformerInputEventKind_ExecutableReload) 473 debug_library_reload(input); 474 input_event.kind = (u64)fw->user_context; 475 } else { 476 input_event.kind = BeamformerInputEventKind_FileEvent; 477 input_event.file_watch_user_context = fw->user_context; 478 } 479 os_push_input_event(input, input_event); 480 } 481 fw->update_time = current_time; 482 break; 483 } 484 485 offset = fni->next_entry_offset; 486 fni = (w32_file_notify_info *)((u8 *)fni + offset); 487 } while (offset); 488 } 489 490 function void 491 clear_io_queue(BeamformerInput *input, Arena arena) 492 { 493 iptr handle = os_w32_context.io_completion_handle; 494 w32_overlapped *overlapped; 495 u32 bytes_read; 496 uptr user_data; 497 498 u64 current_time = os_timer_count(); 499 500 while (GetQueuedCompletionStatus(handle, &bytes_read, &user_data, &overlapped, 0)) { 501 w32_io_completion_event *event = (w32_io_completion_event *)user_data; 502 switch (event->tag) { 503 case W32IOEvent_FileWatch:{ 504 OSW32FileWatchDirectory *dir = (OSW32FileWatchDirectory *)event->context; 505 dispatch_file_watch(input, arena, current_time, dir); 506 zero_struct(&dir->overlapped); 507 ReadDirectoryChangesW(dir->handle, dir->buffer, OSW32_FileWatchDirectoryBufferSize, 0, 508 FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &dir->overlapped, 0); 509 }break; 510 } 511 } 512 } 513 514 extern i32 515 main(void) 516 { 517 os_w32_context.error_handle = GetStdHandle(STD_ERROR_HANDLE); 518 os_w32_context.io_completion_handle = CreateIoCompletionPort(INVALID_FILE, 0, 0, 0); 519 os_w32_context.system_info.timer_frequency = os_timer_frequency(); 520 os_w32_context.system_info.path_separator_byte = '\\'; 521 { 522 w32_system_info info = {0}; 523 GetSystemInfo(&info); 524 525 os_w32_context.system_info.page_size = info.page_size; 526 os_w32_context.system_info.logical_processor_count = info.number_of_processors; 527 } 528 529 Arena program_memory = os_alloc_arena(MB(16) + MB(2)); 530 os_w32_context.arena = sub_arena(&program_memory, MB(2), os_w32_context.system_info.page_size); 531 532 BeamformerInput *input = push_struct(&program_memory, BeamformerInput); 533 os_w32_context.input = input; 534 input->memory = program_memory.beg; 535 input->memory_size = program_memory.end - program_memory.beg; 536 input->shared_memory = allocate_shared_memory(OS_SHARED_MEMORY_NAME, OS_SHARED_MEMORY_SIZE, 537 &input->shared_memory_size); 538 if (input->shared_memory) { 539 input->shared_memory_name = str8(OS_SHARED_MEMORY_NAME).data; 540 input->shared_memory_name_length = str8(OS_SHARED_MEMORY_NAME).length; 541 } 542 543 os_push_input_event(input, (BeamformerInputEvent){ 544 .kind = BeamformerInputEventKind_ExecutableReload, 545 }); 546 547 load_platform_libraries(input); 548 549 beamformer_init(input); 550 551 while (!WindowShouldClose() && !beamformer_should_close(input)) { 552 os_build_frame_input(input); 553 554 DeferLoop(take_lock(&os_w32_context.arena_lock, -1), release_lock(&os_w32_context.arena_lock)) 555 { 556 clear_io_queue(input, os_w32_context.arena); 557 } 558 559 beamformer_frame_step(input); 560 561 // NOTE(rnp): this must happen at the end of frame to allow the pre loop events through 562 // TODO(rnp): hack: until raylib is removed this happens in ui since raylib will cause 563 // glfw to call the input callbacks in during EndDrawing() 564 //input->event_count = 0; 565 } 566 567 beamformer_terminate(input); 568 }