#include "sys.h" #include "memory.h" #include "app.h" #include "string.h" #include "arena.h" #include "scratch.h" #include "atomic.h" #include "work.h" #include "log.h" #include "math.h" #include "util.h" #include "thread_local.h" #include "uni.h" #pragma warning(push, 0) # define UNICODE # include # include # include # include # include # include #pragma warning(pop) #pragma comment(lib, "kernel32") #pragma comment(lib, "user32") #pragma comment(lib, "shell32") #pragma comment(lib, "ole32") #pragma comment(lib, "advapi32") #pragma comment(lib, "winmm") #pragma comment(lib, "dwmapi") #define SYS_WINDOW_EVENT_LISTENERS_MAX 512 #define WINDOW_CLASS_NAME L"power_play_window_class" struct win32_thread { u64 gen; sys_thread_entry_point_func *entry_point; void *thread_data; char thread_name_cstr[256]; struct win32_thread *next; struct win32_thread *prev; HANDLE handle; }; struct win32_condition_variable { CONDITION_VARIABLE condition_variable; struct win32_condition_variable *next_free; }; enum win32_window_cursor_set_flag { WIN32_WINDOW_CURSOR_SET_FLAG_NONE = 0x0, WIN32_WINDOW_CURSOR_SET_FLAG_POSITION = 0x1, WIN32_WINDOW_CURSOR_SET_FLAG_HIDE = 0x2, WIN32_WINDOW_CURSOR_SET_FLAG_SHOW = 0x4, WIN32_WINDOW_CURSOR_SET_FLAG_ENABLE_CLIP = 0X8, WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP = 0X10 }; struct win32_window { u32 flags; HWND hwnd; u32 tid; struct sync_flag ready_sf; u16 utf16_high_surrogate_last_input; struct sys_mutex settings_mutex; struct sys_window_settings settings; i32 monitor_width; i32 monitor_height; /* NOTE: width & height are unaffected by window minimization (they retain * their pre-minimized values) */ i32 x, y, width, height; /* FIXME: Use a cmd buffer for updating cursor (and maybe also settings). * Current setup means cursor_set calls can be applied out of order */ u32 cursor_set_flags; struct v2 cursor_set_position; struct rect cursor_clip_bounds; b32 event_thread_shutdown; struct sys_thread event_thread; struct sys_mutex event_callbacks_mutex; sys_window_event_callback_func *event_callbacks[SYS_WINDOW_EVENT_LISTENERS_MAX]; u64 event_callbacks_count; struct win32_window *next_free; }; /* ========================== * * Global state * ========================== */ GLOBAL struct { SYSTEM_INFO info; LARGE_INTEGER timer_frequency; LARGE_INTEGER timer_start; i32 scheduler_period_ms; DWORD thread_tls_index; u32 main_thread_id; /* Panic */ struct atomic_i32 panicking; wchar_t panic_wstr[4096]; HANDLE panic_event; /* Lookup tables */ enum sys_btn vk_btn_table[256]; /* Condition variables */ struct sys_mutex condition_variables_mutex; struct arena condition_variables_arena; struct win32_condition_variable *first_free_condition_variable; /* Thread params */ struct sys_mutex threads_mutex; struct arena threads_arena; struct win32_thread *threads_first; struct win32_thread *threads_last; struct win32_thread *threads_first_free; /* Windows */ WNDCLASSEXW window_class; struct sys_mutex windows_mutex; struct arena windows_arena; struct win32_window *first_free_window; } G = { 0 }, DEBUG_ALIAS(G, G_sys_win32); /* ========================== * * Events * ========================== */ /* https://git.rfleury.com/community/root_basic/src/commit/9b49fcd24e0c3f875b7c213e81a219bf8544bddb/code/os/gfx/win32/os_gfx_win32.c#L193 */ INTERNAL void win32_init_vk_btn_table(void) { MEMZERO_ARRAY(G.vk_btn_table); for (u32 i = 'A', j = SYS_BTN_A; i <= 'Z'; i += 1, j += 1) { G.vk_btn_table[i] = (enum sys_btn)j; } for (u32 i = '0', j = SYS_BTN_0; i <= '9'; i += 1, j += 1) { G.vk_btn_table[i] = (enum sys_btn)j; } for (u32 i = VK_F1, j = SYS_BTN_F1; i <= VK_F24; i += 1, j += 1) { G.vk_btn_table[i] = (enum sys_btn)j; } G.vk_btn_table[VK_ESCAPE] = SYS_BTN_ESC; G.vk_btn_table[VK_OEM_3] = SYS_BTN_GRAVE_ACCENT; G.vk_btn_table[VK_OEM_MINUS] = SYS_BTN_MINUS; G.vk_btn_table[VK_OEM_PLUS] = SYS_BTN_EQUAL; G.vk_btn_table[VK_BACK] = SYS_BTN_BACKSPACE; G.vk_btn_table[VK_TAB] = SYS_BTN_TAB; G.vk_btn_table[VK_SPACE] = SYS_BTN_SPACE; G.vk_btn_table[VK_RETURN] = SYS_BTN_ENTER; G.vk_btn_table[VK_CONTROL] = SYS_BTN_CTRL; G.vk_btn_table[VK_SHIFT] = SYS_BTN_SHIFT; G.vk_btn_table[VK_MENU] = SYS_BTN_ALT; G.vk_btn_table[VK_UP] = SYS_BTN_UP; G.vk_btn_table[VK_LEFT] = SYS_BTN_LEFT; G.vk_btn_table[VK_DOWN] = SYS_BTN_DOWN; G.vk_btn_table[VK_RIGHT] = SYS_BTN_RIGHT; G.vk_btn_table[VK_DELETE] = SYS_BTN_DELETE; G.vk_btn_table[VK_PRIOR] = SYS_BTN_PAGE_UP; G.vk_btn_table[VK_NEXT] = SYS_BTN_PAGE_DOWN; G.vk_btn_table[VK_HOME] = SYS_BTN_HOME; G.vk_btn_table[VK_END] = SYS_BTN_END; G.vk_btn_table[VK_OEM_2] = SYS_BTN_FORWARD_SLASH; G.vk_btn_table[VK_OEM_PERIOD] = SYS_BTN_PERIOD; G.vk_btn_table[VK_OEM_COMMA] = SYS_BTN_COMMA; G.vk_btn_table[VK_OEM_7] = SYS_BTN_QUOTE; G.vk_btn_table[VK_OEM_4] = SYS_BTN_LEFT_BRACKET; G.vk_btn_table[VK_OEM_6] = SYS_BTN_RIGHT_BRACKET; G.vk_btn_table[VK_INSERT] = SYS_BTN_INSERT; G.vk_btn_table[VK_OEM_1] = SYS_BTN_SEMICOLON; } /* ========================== * * Memory * ========================== */ void *sys_memory_reserve(u64 size) { void *ptr = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS); return ptr; } void sys_memory_release(void *address) { VirtualFree(address, 0, MEM_RELEASE); } void *sys_memory_commit(void *address, u64 size) { void *ptr = VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE); return ptr; } void sys_memory_decommit(void *address, u64 size) { VirtualFree(address, size, MEM_DECOMMIT); } void sys_memory_set_committed_readonly(void *address, u64 size) { DWORD old = PAGE_READWRITE; VirtualProtect(address, size, PAGE_READONLY, &old); } /* ========================== * * Time * ========================== */ INTERNAL struct sys_datetime win32_time_to_sys_time(SYSTEMTIME st) { return (struct sys_datetime) { .year = st.wYear, .month = st.wMonth, .day_of_week = st.wDayOfWeek, .day = st.wDay, .hour = st.wHour, .minute = st.wMinute, .second = st.wSecond, .milliseconds = st.wMilliseconds }; } /* prevent 64-bit overflow when computing relative timestamp * https://github.com/floooh/sokol/blob/d4ac122f36d7659a18b312fd4fa2317fb9e06a63/sokol_time.h#L203 */ INTERNAL i64 _win32_i64_muldiv(i64 value, i64 numer, i64 denom) { i64 q = value / denom; i64 r = value % denom; return q * numer + r * numer / denom; } sys_timestamp_t sys_timestamp(void) { LARGE_INTEGER time; QueryPerformanceCounter(&time); return (u64)_win32_i64_muldiv(time.QuadPart - G.timer_start.QuadPart, 1000000000, G.timer_frequency.QuadPart); } f64 sys_timestamp_seconds(sys_timestamp_t ts) { return (f64)ts / 1000000000.0; } struct sys_datetime sys_local_time(void) { SYSTEMTIME lt; GetLocalTime(<); return win32_time_to_sys_time(lt); } /* ========================== * * File system * ========================== */ INTERNAL struct string string_from_win32_path(struct arena *arena, wchar_t *src) { struct string res = { .len = 0, .text = arena_dry_push(arena, u8) }; while (*src) { struct string16 decode_str = { .len = *(src + 1) ? 2 : 1, .text = src }; struct uni_decode_utf16_result decoded = uni_decode_utf16(decode_str); struct uni_encode_utf8_result encoded = uni_encode_utf8(decoded.codepoint); u8 *dest = arena_push_array(arena, u8, encoded.count8); for (u32 i = 0; i < encoded.count8; ++i) { u8 byte = encoded.chars8[i]; if (byte == '\\') { byte = '/'; } dest[i] = byte; } res.len += encoded.count8; src += decoded.advance16; } return res; } struct string sys_get_write_path(struct arena *arena) { u16 *p = NULL; /* TODO: cache this? */ HRESULT res = SHGetKnownFolderPath( &FOLDERID_LocalAppData, 0, NULL, &p ); struct string path = { 0 }; if (res == S_OK) { path = string_from_win32_path(arena, p); } CoTaskMemFree(p); return path; } b32 sys_is_file(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); DWORD attributes = GetFileAttributesW(path_wstr); scratch_end(scratch); return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); } b32 sys_is_dir(struct string path) { struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); DWORD attributes = GetFileAttributesW(path_wstr); scratch_end(scratch); return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY); } void sys_mkdir(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); int err_code = SHCreateDirectory(NULL, path_wstr); struct string err = { 0 }; switch (err_code) { case ERROR_BAD_PATHNAME: { err = STR("Bad path name"); } break; case ERROR_FILENAME_EXCED_RANGE: { err = STR("Path name too long"); } break; case ERROR_FILE_EXISTS: { err = STR("A file already exists at this location"); } break; case ERROR_CANCELLED: { err = STR("User canceled the operation"); } break; default: break; } if (err.len > 0) { struct string msg = string_format(scratch.arena, STR("Failed to create directory \"%F\": %F"), FMT_STR(path), FMT_STR(err)); sys_panic(msg); } scratch_end(scratch); } struct sys_file sys_file_open_read(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); HANDLE handle = CreateFileW( path_wstr, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); scratch_end(scratch); struct sys_file file = { (u64)handle }; return file; } struct sys_file sys_file_open_read_wait(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); i32 delay_ms = 1; HANDLE handle; while ((handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_SHARING_VIOLATION) { __profscope(file_share_conflict_delay); Sleep(delay_ms); if (delay_ms < 1024) { delay_ms *= 2; } } else { break; } } scratch_end(scratch); return (struct sys_file) { (u64)handle }; } struct sys_file sys_file_open_write(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ HANDLE handle = CreateFileW( path_wstr, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); scratch_end(scratch); return (struct sys_file) { (u64)handle }; } struct sys_file sys_file_open_append(struct string path) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *path_wstr = wstr_from_string(scratch.arena, path); /* TODO: Handle errors / non-existing file (handle == INVALID_HANDLE_VALUE) */ HANDLE handle = CreateFileW( path_wstr, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); scratch_end(scratch); struct sys_file file = (struct sys_file) { (u64)handle }; return file; } void sys_file_close(struct sys_file file) { __prof; CloseHandle((HANDLE)file.handle); } struct buffer sys_file_read_all(struct arena *arena, struct sys_file file) { __prof; i64 size = 0; GetFileSizeEx((HANDLE)file.handle, (PLARGE_INTEGER)&size); struct buffer buff = { .size = size, .data = NULL }; if (size > 0) { /* ReadFile returns non-zero on success */ /* TODO: error checking */ arena_align(arena, 16); buff.data = arena_push_array(arena, u8, size); (UNUSED)ReadFile( (HANDLE)file.handle, buff.data, (DWORD)buff.size, NULL, /* lpNumberOfBytesRead */ NULL ); } return buff; } void sys_file_write(struct sys_file file, struct buffer data) { __prof; /* TODO: Check what the real data limit is and chunk sequentially based on * that (rather than failing) */ if (data.size >= 0x7FFF) { struct temp_arena scratch = scratch_begin_no_conflict(); sys_panic(string_format(scratch.arena, STR("Tried to write too many bytes to disk (%F)"), FMT_UINT(data.size))); scratch_end(scratch); } /* WriteFile returns TRUE on success */ (UNUSED)WriteFile( (HANDLE)file.handle, data.data, (DWORD)data.size, NULL, /* lpNumberOfBytesWritten */ NULL ); } u64 sys_file_get_size(struct sys_file file) { LARGE_INTEGER li_file_size; GetFileSizeEx((HANDLE)file.handle, &li_file_size); return (u64)(li_file_size.QuadPart > 0 ? li_file_size.QuadPart : 0); } struct sys_file_time sys_file_get_time(struct sys_file file) { __prof; /* Get file times */ FILETIME ft_created; FILETIME ft_accessed; FILETIME ft_modified; b32 success = !!GetFileTime((HANDLE)file.handle, &ft_created, &ft_accessed, &ft_modified); if (success) { /* Convert file times to local file time */ FileTimeToLocalFileTime(&ft_created, &ft_created); FileTimeToLocalFileTime(&ft_accessed, &ft_accessed); FileTimeToLocalFileTime(&ft_modified, &ft_modified); /* Convert local file times to system times */ SYSTEMTIME st_created; SYSTEMTIME st_accessed; SYSTEMTIME st_modified; FileTimeToSystemTime(&ft_created, &st_created); FileTimeToSystemTime(&ft_accessed, &st_accessed); FileTimeToSystemTime(&ft_modified, &st_modified); return (struct sys_file_time) { .created = win32_time_to_sys_time(st_created), .accessed = win32_time_to_sys_time(st_accessed), .modified = win32_time_to_sys_time(st_modified) }; } else { return (struct sys_file_time) { 0 }; } } /* ========================== * * File map * ========================== */ struct sys_file_map sys_file_map_open_read(struct sys_file file) { __prof; u64 size = sys_file_get_size(file); u8 *base_ptr = NULL; HANDLE map_handle = 0; if (size > 0) { map_handle = CreateFileMappingW( (HANDLE)file.handle, NULL, PAGE_READONLY, 0, 0, NULL ); if (!map_handle) { ASSERT(false); return (struct sys_file_map) { 0 }; } base_ptr = MapViewOfFile( map_handle, FILE_MAP_READ, 0, 0, 0 ); if (!base_ptr) { /* Failed to create view */ ASSERT(false); CloseHandle(map_handle); return (struct sys_file_map) { 0 }; } } else { /* File is empty */ ASSERT(false); return (struct sys_file_map) { 0 }; } return (struct sys_file_map) { .handle = (u64)map_handle, .mapped_memory = BUFFER(size, base_ptr) }; } void sys_file_map_close(struct sys_file_map map) { if (map.mapped_memory.data) { UnmapViewOfFile(map.mapped_memory.data); } if (map.handle) { CloseHandle((HANDLE)map.handle); } } struct buffer sys_file_map_data(struct sys_file_map map) { return map.mapped_memory; } /* ========================== * * File iter * ========================== */ struct win32_file_filter { HANDLE find_handle; wchar_t *filter_wstr; }; struct sys_file_filter sys_file_filter_begin(struct arena *arena, struct string pattern) { struct sys_file_filter filter = { 0 }; struct win32_file_filter *filter_internal = arena_push_zero(arena, struct win32_file_filter); filter_internal->filter_wstr = wstr_from_string(arena, pattern); filter.handle = (u64)filter_internal; return filter; } b32 sys_file_filter_next(struct arena *arena, struct sys_file_filter *filter) { struct win32_file_filter *filter_internal = (struct win32_file_filter *)filter->handle; WIN32_FIND_DATAW find_file_data = { 0 }; b32 found = false; if (filter_internal->find_handle) { found = FindNextFileW(filter_internal->find_handle, &find_file_data); } else { filter_internal->find_handle = FindFirstFileExW(filter_internal->filter_wstr, FindExInfoStandard, &find_file_data, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE | FIND_FIRST_EX_LARGE_FETCH); found = filter_internal->find_handle != INVALID_HANDLE_VALUE; } if (found) { struct string file_name = string_from_wstr(arena, find_file_data.cFileName); if (string_eq(file_name, STR(".")) || string_eq(file_name, STR(".."))) { /* Skip initial '.' and '..' matches */ found = sys_file_filter_next(arena, filter); } else { filter->info.is_dir = find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; filter->info.filename = string_copy(arena, file_name); } } return found; } void sys_file_filter_end(struct sys_file_filter *filter) { struct win32_file_filter *filter_internal = (struct win32_file_filter *)filter->handle; if (filter_internal->find_handle) { FindClose(filter_internal->find_handle); } } /* ========================== * * Window * ========================== */ INTERNAL void win32_update_window_from_system(struct win32_window *window); INTERNAL void win32_window_process_event(struct win32_window *window, struct sys_event event) { struct sys_lock lock = sys_mutex_lock_e(&window->event_callbacks_mutex); for (u64 i = 0; i < window->event_callbacks_count; ++i) { window->event_callbacks[i](event); } sys_mutex_unlock(&lock); } INTERNAL HWND win32_create_window(struct win32_window *window) { /* * From martins (https://gist.github.com/mmozeiko/5e727f845db182d468a34d524508ad5f#file-win32_d3d11-c-L66-L70): * WS_EX_NOREDIRECTIONBITMAP flag here is needed to fix ugly bug with Windows 10 * when window is resized and DXGI swap chain uses FLIP presentation model * DO NOT use it if you choose to use non-FLIP presentation model * read about the bug here: https://stackoverflow.com/q/63096226 and here: https://stackoverflow.com/q/53000291 */ DWORD exstyle = WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP; /* TODO: Check for hwnd success */ HWND hwnd = CreateWindowExW( exstyle, G.window_class.lpszClassName, L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, G.window_class.hInstance, NULL ); /* Dark mode */ BOOL dark_mode = true; DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, (LPCVOID)&dark_mode, sizeof(dark_mode)); /* Set window as userdata */ SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)window); return hwnd; } INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(window_thread_entry_point, arg) { struct win32_window *window = (struct win32_window *)arg; /* Win32 limitation: Window must be initialized on same thread that processes events */ window->hwnd = win32_create_window(window); window->tid = sys_thread_id(); win32_update_window_from_system(window); BringWindowToTop(window->hwnd); sync_flag_set(&window->ready_sf); while (!window->event_thread_shutdown) { MSG msg = {0}; GetMessageW(&msg, 0, 0, 0); /* Update cursor */ if (GetFocus() == window->hwnd) { u32 cursor_flags = window->cursor_set_flags; /* Hide cursor */ if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_HIDE) { while(ShowCursor(false) >= 0); } /* Show cursor */ if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_SHOW) { while(ShowCursor(true) < 0); } /* Update position */ if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_POSITION) { struct v2 window_space_pos = window->cursor_set_position; POINT p = { window_space_pos.x, window_space_pos.y }; ClientToScreen(window->hwnd, &p); SetCursorPos(p.x, p.y); } /* Stop clipping cursor */ if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP) { ClipCursor(NULL); } /* Clip cursor in window window */ if (cursor_flags & WIN32_WINDOW_CURSOR_SET_FLAG_ENABLE_CLIP) { i32 left = window->x + math_round_to_int(window->cursor_clip_bounds.x); i32 right = left + math_round_to_int(window->cursor_clip_bounds.width); i32 top = window->y + math_round_to_int(window->cursor_clip_bounds.y); i32 bottom = top + math_round_to_int(window->cursor_clip_bounds.height); RECT clip = { .left = clamp_i32(left, window->x, window->x + window->width), .right = clamp_i32(right, window->x, window->x + window->width), .top = clamp_i32(top, window->y, window->y + window->height), .bottom = clamp_i32(bottom, window->y, window->y + window->height) }; ClipCursor(&clip); } window->cursor_set_flags = 0; } /* Process message */ switch (msg.message) { case WM_QUIT: { win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_QUIT }); } break; default: { TranslateMessage(&msg); DispatchMessageW(&msg); } break; } } /* Destroy window hwnd */ DestroyWindow(window->hwnd); } INTERNAL struct win32_window *win32_window_alloc(void) { struct win32_window *window = NULL; { struct sys_lock lock = sys_mutex_lock_e(&G.windows_mutex); if (G.first_free_window) { window = G.first_free_window; G.first_free_window = window->next_free; } else { window = arena_push(&G.windows_arena, struct win32_window); } sys_mutex_unlock(&lock); } MEMZERO_STRUCT(window); /* Allocate sync flag */ window->ready_sf = sync_flag_alloc(); /* Allocate mutexes */ window->settings_mutex = sys_mutex_alloc(); window->event_callbacks_mutex = sys_mutex_alloc(); /* Start window thread for processing events */ window->event_thread = sys_thread_alloc(&window_thread_entry_point, window, STR("[P8] Window thread")); /* Wait for event thread to create actual window */ sync_flag_wait(&window->ready_sf); return window; } INTERNAL void win32_window_release(struct win32_window *window) { struct sys_lock lock = sys_mutex_lock_e(&G.windows_mutex); window->next_free = G.first_free_window; G.first_free_window = window; /* Stop window thread */ window->event_thread_shutdown = true; sys_thread_wait_release(&window->event_thread); /* Release mutexes */ sys_mutex_release(&window->event_callbacks_mutex); sys_mutex_release(&window->settings_mutex); /* Release sync flag */ sync_flag_release(&window->ready_sf); sys_mutex_unlock(&lock); } INTERNAL void win32_update_window_from_system(struct win32_window *window) { HWND hwnd = window->hwnd; RECT window_rect = {0}; GetWindowRect(hwnd, &window_rect); RECT client_rect = {0}; GetClientRect(hwnd, (LPRECT)&client_rect); ClientToScreen(hwnd, (LPPOINT)&client_rect.left); ClientToScreen(hwnd, (LPPOINT)&client_rect.right); /* TODO: Error if we can't get monitor info */ /* Screen dimensions */ MONITORINFO monitor_info = {sizeof(monitor_info)}; GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info); RECT monitor_rect = monitor_info.rcMonitor; window->monitor_width = monitor_rect.right - monitor_rect.left; window->monitor_height = monitor_rect.bottom - monitor_rect.top; /* Minimized / maximized */ if (window->flags & SYS_WINDOW_FLAG_SHOWING) { WINDOWPLACEMENT placement = {sizeof(placement)}; GetWindowPlacement(hwnd, &placement); if (placement.showCmd == SW_SHOWMINIMIZED) { window->settings.flags |= SYS_WINDOW_SETTINGS_FLAG_MINIMIZED; } else { window->settings.flags &= ~SYS_WINDOW_SETTINGS_FLAG_MINIMIZED; } if (placement.showCmd == SW_SHOWMAXIMIZED || ((window->settings.flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) && ((placement.flags & WPF_RESTORETOMAXIMIZED) != 0))) { window->settings.flags |= SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED; } else { window->settings.flags &= ~SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED; } } /* Window dimensions */ i32 x = client_rect.left; i32 y = client_rect.top; i32 width = client_rect.right - client_rect.left; i32 height = client_rect.bottom - client_rect.top; if (!(window->settings.flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED)) { window->x = x; window->y = y; window->width = width; window->height = height; if (!(window->settings.flags & (SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED | SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN))) { /* Treat a window resize in non maximized/fullscreen mode as a * settings change. * * TODO: make sure we check for fullscreen here too if we ever * allow it. */ window->settings.floating_x = x; window->settings.floating_y = y; window->settings.floating_width = width; window->settings.floating_height = height; } } } INTERNAL void win32_update_window_from_settings(struct win32_window *window, struct sys_window_settings *settings) { HWND hwnd = window->hwnd; struct sys_window_settings old_settings = window->settings; window->settings = *settings; i32 show_cmd = SW_HIDE; if (window->flags & SYS_WINDOW_FLAG_SHOWING) { show_cmd = SW_NORMAL; if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) { show_cmd = SW_SHOWMAXIMIZED; } else if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) { show_cmd = SW_MINIMIZE; } } RECT rect = { 0 }; b32 old_fullscreen = old_settings.flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; b32 fullscreen = settings->flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; if (fullscreen) { if (!old_fullscreen) { /* Entering fullscreen */ SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP); } rect = (RECT) { .left = 0, .top = 0, .right = window->monitor_width, .bottom = window->monitor_height }; } else { if (old_fullscreen) { /* Leaving fullscreen */ SetWindowLongPtrW(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); } rect = (RECT) { .left = settings->floating_x, .top = settings->floating_y, .right = settings->floating_x + settings->floating_width, .bottom = settings->floating_y + settings->floating_height }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false); } WINDOWPLACEMENT wp = { .length = sizeof(WINDOWPLACEMENT), .showCmd = show_cmd, .rcNormalPosition = rect }; SetWindowPlacement(hwnd, &wp); { struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *title_wstr = wstr_from_string(scratch.arena, string_from_cstr(settings->title)); SetWindowTextW(hwnd, title_wstr); scratch_end(scratch); } } INTERNAL void win32_window_wake(struct win32_window *window) { /* Post a blank message to the window's thread message queue to wake it. */ PostMessageW(window->hwnd, WM_NULL, 0, 0); } INTERNAL LRESULT CALLBACK win32_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { struct win32_window *window = (struct win32_window *)GetWindowLongPtrW(hwnd, GWLP_USERDATA); if (!window) { return DefWindowProcW(hwnd, msg, wparam, lparam); } LRESULT result = 0; b32 is_release = false; switch (msg) { case WM_CLOSE: case WM_DESTROY: { win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_QUIT }); } break; case WM_PAINT: { result = DefWindowProcW(hwnd, msg, wparam, lparam); } break; case WM_MOVE: case WM_MOVING: case WM_SIZE: case WM_SIZING: { win32_update_window_from_system(window); result = DefWindowProcW(hwnd, msg, wparam, lparam); } break; /* Keyboard buttons */ case WM_SYSKEYUP: case WM_SYSKEYDOWN: { result = DefWindowProcW(hwnd, msg, wparam, lparam); } FALLTHROUGH; case WM_KEYUP: case WM_KEYDOWN: { WORD vk_code = LOWORD(wparam); b32 is_repeat = false; enum sys_event_kind event_kind = SYS_EVENT_KIND_NONE; if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) { event_kind = SYS_EVENT_KIND_BUTTON_DOWN; is_repeat = (lparam & 0x40000000) != 0; } else if (msg == WM_KEYUP || msg == WM_SYSKEYUP) { event_kind = SYS_EVENT_KIND_BUTTON_UP; } enum sys_btn button = SYS_BTN_NONE; if (vk_code < ARRAY_COUNT(G.vk_btn_table)) { button = G.vk_btn_table[vk_code]; } win32_window_process_event( window, (struct sys_event) { .kind = event_kind, .button = button, .is_repeat = is_repeat } ); } break; /* Text */ case WM_SYSCHAR: case WM_CHAR: { u16 utf16_char = (u32)wparam; /* Decode */ u32 codepoint = 0; if (uni_is_utf16_high_surrogate(utf16_char)) { window->utf16_high_surrogate_last_input = utf16_char; } else if (uni_is_utf16_low_surrogate(utf16_char)) { u16 high = window->utf16_high_surrogate_last_input; u16 low = utf16_char; if (high) { u16 utf16_pair_bytes[2] = { high, low }; struct uni_decode_utf16_result decoded = uni_decode_utf16((struct string16) { .len = ARRAY_COUNT(utf16_pair_bytes), .text = utf16_pair_bytes }); if (decoded.advance16 == 2 && decoded.codepoint < U32_MAX) { codepoint = decoded.codepoint; } } window->utf16_high_surrogate_last_input = 0; } else { window->utf16_high_surrogate_last_input = 0; codepoint = utf16_char; } if (codepoint) { if (codepoint == '\r') { codepoint = '\n'; /* Just treat all \r as newline */ } if((codepoint >= 32 && codepoint != 127) || codepoint == '\t' || codepoint == '\n') { win32_window_process_event( window, (struct sys_event) { .kind = SYS_EVENT_KIND_TEXT, .text_codepoint = codepoint } ); } } } break; /* Mouse buttons */ case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: case WM_XBUTTONUP: { ReleaseCapture(); is_release = true; } FALLTHROUGH; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_XBUTTONDOWN: { if(!is_release) { SetCapture(hwnd); } enum sys_event_kind event_kind = is_release ? SYS_EVENT_KIND_BUTTON_UP : SYS_EVENT_KIND_BUTTON_DOWN; enum sys_btn button = 0; switch(msg) { case WM_LBUTTONUP: case WM_LBUTTONDOWN: button = SYS_BTN_M1; break; case WM_RBUTTONUP: case WM_RBUTTONDOWN: button = SYS_BTN_M2; break; case WM_MBUTTONUP: case WM_MBUTTONDOWN: button = SYS_BTN_M3; break; case WM_XBUTTONUP: case WM_XBUTTONDOWN: { u32 wparam_xbutton = GET_XBUTTON_WPARAM(wparam); if (wparam_xbutton == XBUTTON1) { button = SYS_BTN_M3; } else if (wparam_xbutton == XBUTTON2) { button = SYS_BTN_M4; } } break; } if (button) { win32_window_process_event( window, (struct sys_event) { .kind = event_kind, .button = button } ); } } break; /* Mouse wheel */ case WM_MOUSEWHEEL: { int delta = GET_WHEEL_DELTA_WPARAM(wparam); i32 dir = delta >= 0 ? 1 : -1; enum sys_btn button = dir >= 0 ? SYS_BTN_MWHEELUP : SYS_BTN_MWHEELDOWN; for (i32 i = 0; i < (dir * delta); i += WHEEL_DELTA) { /* Send a button down & button up event simultaneously */ win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_BUTTON_DOWN, .button = button }); win32_window_process_event(window, (struct sys_event) { .kind = SYS_EVENT_KIND_BUTTON_UP, .button = button }); } } break; /* Mouse move */ case WM_MOUSEMOVE: { i32 x = GET_X_LPARAM(lparam); i32 y = GET_Y_LPARAM(lparam); win32_window_process_event( window, (struct sys_event) { .kind = SYS_EVENT_KIND_CURSOR_MOVE, .cursor_position = V2(x, y) } ); } break; /* Raw mouse move */ case WM_INPUT: { struct temp_arena scratch = scratch_begin_no_conflict(); /* Read raw input buffer */ UINT buff_size; GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &buff_size, sizeof(RAWINPUTHEADER)); u8 *buff = arena_push_array_zero(scratch.arena, u8, buff_size); if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) != buff_size) { logf_error("GetRawInputData did not return correct size"); break; } RAWINPUT *raw = (RAWINPUT *)buff; if (raw->header.dwType == RIM_TYPEMOUSE) { i32 x = raw->data.mouse.lLastX; i32 y = raw->data.mouse.lLastY; struct v2 delta = V2(x, y); win32_window_process_event( window, (struct sys_event) { .kind = SYS_EVENT_KIND_MOUSE_MOVE, .mouse_delta = delta } ); } scratch_end(scratch); } break; default: { result = DefWindowProcW(hwnd, msg, wparam, lparam); } break; } return result; } struct sys_window sys_window_alloc(void) { __prof; struct sys_window w = { .handle = (u64)win32_window_alloc() }; return w; } void sys_window_release(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; win32_window_release(window); } void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func) { struct win32_window *window = (struct win32_window *)sys_window->handle; struct sys_lock lock = sys_mutex_lock_e(&window->event_callbacks_mutex); { if (window->event_callbacks_count + 1 > ARRAY_COUNT(window->event_callbacks)) { sys_panic(STR("Too many window event callbacks registered")); } else { window->event_callbacks[window->event_callbacks_count++] = func; } } sys_mutex_unlock(&lock); } void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func) { struct win32_window *window = (struct win32_window *)sys_window->handle; struct sys_lock lock = sys_mutex_lock_e(&window->event_callbacks_mutex); { u64 count = window->event_callbacks_count; sys_window_event_callback_func *last = count > 0 ? window->event_callbacks[count - 1] : NULL; for (u64 i = 0; i < window->event_callbacks_count; ++i) { if (window->event_callbacks[i] == func) { if (func != last) { /* Swap with last element */ window->event_callbacks[i] = last; } --window->event_callbacks_count; } } } sys_mutex_unlock(&lock); } void sys_window_update_settings(struct sys_window *sys_window, struct sys_window_settings *settings) { __prof; struct win32_window *window = (struct win32_window *)sys_window->handle; struct sys_lock lock = sys_mutex_lock_e(&window->settings_mutex); { win32_update_window_from_settings(window, settings); } sys_mutex_unlock(&lock); } /* FIXME: Lock settings mutex for these functions */ struct sys_window_settings sys_window_get_settings(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; return window->settings; } void sys_window_show(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; HWND hwnd = window->hwnd; struct sys_lock lock = sys_mutex_lock_e(&window->settings_mutex); { i32 show_cmd = SW_NORMAL; struct sys_window_settings *settings = &window->settings; if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) { show_cmd = SW_SHOWMAXIMIZED; } else if (settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED) { show_cmd = SW_MINIMIZE; } window->flags |= SYS_WINDOW_FLAG_SHOWING; ShowWindow(hwnd, show_cmd); BringWindowToTop(hwnd); } sys_mutex_unlock(&lock); } struct v2 sys_window_get_size(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; return V2((f32)window->width, (f32)window->height); } struct v2 sys_window_get_monitor_size(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; return V2((f32)window->monitor_width, (f32)window->monitor_height); } u64 sys_window_get_internal_handle(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; return (u64)window->hwnd; } void sys_window_cursor_set_pos(struct sys_window *sys_window, struct v2 pos) { struct win32_window *window = (struct win32_window *)sys_window->handle; window->cursor_set_position = pos; window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_POSITION; win32_window_wake(window); } void sys_window_cursor_show(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_SHOW; win32_window_wake(window); } void sys_window_cursor_hide(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_HIDE; win32_window_wake(window); } void sys_window_cursor_enable_clip(struct sys_window *sys_window, struct rect bounds) { struct win32_window *window = (struct win32_window *)sys_window->handle; window->cursor_clip_bounds = bounds; window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_ENABLE_CLIP; win32_window_wake(window); } void sys_window_cursor_disable_clip(struct sys_window *sys_window) { struct win32_window *window = (struct win32_window *)sys_window->handle; window->cursor_set_flags |= WIN32_WINDOW_CURSOR_SET_FLAG_DISABLE_CLIP; win32_window_wake(window); } /* ========================== * * Mutex * ========================== */ struct sys_mutex sys_mutex_alloc(void) { __prof; SRWLOCK srwlock = SRWLOCK_INIT; struct sys_mutex mutex = { .handle = *(u64 *)&srwlock }; return mutex; } void sys_mutex_release(struct sys_mutex *mutex) { __prof; (UNUSED)mutex; /* Mutex should be unlocked */ ASSERT(atomic_i64_eval(&mutex->count) == 0); } struct sys_lock sys_mutex_lock_e(struct sys_mutex *mutex) { __prof; AcquireSRWLockExclusive((SRWLOCK *)&mutex->handle); #if RTC mutex->owner_tid = (u64)GetCurrentThreadId(); atomic_i64_inc_eval(&mutex->count); #endif struct sys_lock lock = { 0 }; lock.exclusive = true; lock.mutex = mutex; return lock; } struct sys_lock sys_mutex_lock_s(struct sys_mutex *mutex) { __prof; AcquireSRWLockShared((SRWLOCK *)&mutex->handle); #if RTC atomic_i64_inc_eval(&mutex->count); #endif struct sys_lock lock = { 0 }; lock.mutex = mutex; return lock; } void sys_mutex_unlock(struct sys_lock *lock) { __prof; #if RTC atomic_i64_dec_eval(&lock->mutex->count); lock->mutex->owner_tid = 0; #endif if (lock->exclusive) { ReleaseSRWLockExclusive((SRWLOCK *)&lock->mutex->handle); } else { ReleaseSRWLockShared((SRWLOCK *)&lock->mutex->handle); } MEMZERO_STRUCT(lock); } #if RTC void sys_assert_locked_e(struct sys_lock *lock, struct sys_mutex *mutex) { ASSERT(lock->mutex == mutex); ASSERT(lock->exclusive == true); } void sys_assert_locked_s(struct sys_lock *lock, struct sys_mutex *mutex) { ASSERT(lock->mutex == mutex); } #endif /* ========================== * * Condition variable * ========================== */ INTERNAL struct win32_condition_variable *win32_condition_variable_alloc(void) { __prof; struct win32_condition_variable *cv = NULL; { struct sys_lock lock = sys_mutex_lock_e(&G.condition_variables_mutex); if (G.first_free_condition_variable) { cv = G.first_free_condition_variable; G.first_free_condition_variable = cv->next_free; } else { cv = arena_push_zero(&G.condition_variables_arena, struct win32_condition_variable); } sys_mutex_unlock(&lock); } MEMZERO_STRUCT(cv); InitializeConditionVariable(&cv->condition_variable); return cv; } INTERNAL void win32_condition_variable_release(struct win32_condition_variable *w32cv) { __prof; struct sys_lock lock = sys_mutex_lock_e(&G.condition_variables_mutex); w32cv->next_free = G.first_free_condition_variable; G.first_free_condition_variable = w32cv; sys_mutex_unlock(&lock); } struct sys_condition_variable sys_condition_variable_alloc(void) { __prof; struct sys_condition_variable cv = { .handle = (u64)win32_condition_variable_alloc() }; return cv; } void sys_condition_variable_release(struct sys_condition_variable *cv) { __prof; /* Condition variable must not have any sleepers (signal before releasing) */ ASSERT(atomic_i64_eval(&cv->num_waiters) == 0); win32_condition_variable_release((struct win32_condition_variable *)cv->handle); } void sys_condition_variable_wait(struct sys_condition_variable *cv, struct sys_lock *lock) { __prof; struct sys_mutex *mutex = lock->mutex; b32 exclusive = lock->exclusive; #if RTC atomic_i64_inc_eval(&cv->num_waiters); if (exclusive) { mutex->owner_tid = 0; } atomic_i64_dec_eval(&mutex->count); #endif struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle; SleepConditionVariableSRW(&w32cv->condition_variable, (SRWLOCK *)&mutex->handle, INFINITE, exclusive ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED); #if RTC atomic_i64_inc_eval(&mutex->count); if (exclusive) { mutex->owner_tid = (u64)GetCurrentThreadId(); } atomic_i64_dec_eval(&cv->num_waiters); #endif } void sys_condition_variable_wait_time(struct sys_condition_variable *cv, struct sys_lock *lock, f64 seconds) { __prof; struct sys_mutex *mutex = lock->mutex; b32 exclusive = lock->exclusive; #if RTC atomic_i64_inc_eval(&cv->num_waiters); if (exclusive) { mutex->owner_tid = 0; } atomic_i64_dec_eval(&mutex->count); #endif struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle; u32 ms = (u32)math_round_to_int((f32)seconds * 1000.f); SleepConditionVariableSRW(&w32cv->condition_variable, (SRWLOCK *)&mutex->handle, ms, exclusive ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED); #if RTC atomic_i64_inc_eval(&mutex->count); if (exclusive) { mutex->owner_tid = (u64)GetCurrentThreadId(); } atomic_i64_dec_eval(&cv->num_waiters); #endif } void sys_condition_variable_signal(struct sys_condition_variable *cv, u32 count) { __prof; struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle; /* Windows will wake all waiters if many single-wakes occur anyway, so we * might as well wake all ourselves. * https://devblogs.microsoft.com/oldnewthing/20180201-00/?p=97946 */ if (count <= 24) { for (u32 i = 0; i < count; ++i) { WakeConditionVariable(&w32cv->condition_variable); } } else { WakeAllConditionVariable(&w32cv->condition_variable); } } void sys_condition_variable_broadcast(struct sys_condition_variable *cv) { __prof; struct win32_condition_variable *w32cv = (struct win32_condition_variable *)cv->handle; WakeAllConditionVariable(&w32cv->condition_variable); } /* ========================== * * Thread local storage * ========================== */ struct win32_tls { HANDLE sleep_timer; struct thread_local_store store; }; INTERNAL void win32_thread_set_tls(struct win32_tls *ctx) { /* TODO: Fail if error */ TlsSetValue(G.thread_tls_index, (LPVOID)ctx); } INTERNAL struct win32_tls *win32_thread_get_tls(void) { /* TODO: Fail if error */ return TlsGetValue(G.thread_tls_index); } INTERNAL struct win32_tls win32_tls_alloc(void) { struct win32_tls tls = { 0 }; tls.sleep_timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); tls.store = thread_local_store_alloc(); return tls; } INTERNAL void win32_tls_release(struct win32_tls *tls) { thread_local_store_release(&tls->store); CloseHandle(tls->sleep_timer); } struct thread_local_store *sys_thread_get_thread_local_store(void) { struct win32_tls *thread_ctx = (struct win32_tls *)win32_thread_get_tls(); return &thread_ctx->store; } /* ========================== * * Threads * ========================== */ INTERNAL struct win32_thread *win32_thread_alloc_locked(struct sys_lock *lock) { sys_assert_locked_e(lock, &G.threads_mutex); struct win32_thread *t = NULL; if (G.threads_first_free) { t = G.threads_first_free; G.threads_first_free = t->next; MEMZERO_STRUCT(t); } else { t = arena_push_zero(&G.threads_arena, struct win32_thread); t->gen = 1; } if (!G.threads_first) { G.threads_first = t; } else { G.threads_last->next = t; } t->prev = G.threads_last; G.threads_last = t; return t; } INTERNAL void win32_thread_release_locked(struct sys_lock *lock, struct win32_thread *t) { sys_assert_locked_e(lock, &G.threads_mutex); if (t->prev) { t->prev->next = t->next; } if (t->next) { t->next->prev = t->prev; } if (G.threads_first == t) { G.threads_first = t->next; } if (G.threads_last == t) { G.threads_last = t->prev; } u64 new_gen = t->gen + 1; *t = (struct win32_thread) { .next = G.threads_first_free, .gen = new_gen }; } INTERNAL struct win32_thread *win32_thread_from_sys_thread_locked(struct sys_lock *lock, struct sys_thread st) { sys_assert_locked_s(lock, &G.threads_mutex); u64 gen = st.handle[0]; struct win32_thread *t = (struct win32_thread *)st.handle[1]; if (t->gen == gen) { return t; } else { return NULL; } } INTERNAL struct sys_thread sys_thread_from_win32_thread_locked(struct sys_lock *lock, struct win32_thread *t) { sys_assert_locked_s(lock, &G.threads_mutex); return (struct sys_thread) { .handle[0] = t->gen, .handle[1] = (u64)t }; } INTERNAL DWORD WINAPI win32_thread_proc(LPVOID vt) { struct win32_thread *t = (struct win32_thread *)vt; /* Initialize COM */ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); /* Initialize TLS */ struct win32_tls tls = win32_tls_alloc(); win32_thread_set_tls(&tls); /* Set thread name */ struct string thread_name = string_from_cstr(t->thread_name_cstr); if (thread_name.len > 0) { struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *wc_thread_name = wstr_from_string(scratch.arena, thread_name); SetThreadDescription(GetCurrentThread(), wc_thread_name); scratch_end(scratch); } logf_info("New thread \"%F\" created with ID %F", FMT_STR(thread_name), FMT_UINT(sys_thread_id())); /* Start thread */ t->entry_point(t->thread_data); /* Release thread object */ struct sys_lock lock = sys_mutex_lock_e(&G.threads_mutex); { win32_thread_release_locked(&lock, t); } sys_mutex_unlock(&lock); /* Release TLS */ win32_tls_release(&tls); /* Uninitialize COM */ CoUninitialize(); return 0; } struct sys_thread sys_thread_alloc(sys_thread_entry_point_func *entry_point, void *thread_data, struct string thread_name) { ASSERT(entry_point != NULL); logf_info("Creating thread \"%F\"", FMT_STR(thread_name)); struct sys_thread res = { 0 }; struct sys_lock lock = sys_mutex_lock_e(&G.threads_mutex); { /* Allocate thread object */ struct win32_thread *t = win32_thread_alloc_locked(&lock); t->entry_point = entry_point; t->thread_data = thread_data; /* Copy thread name to params */ cstr_buff_from_string(BUFFER_FROM_ARRAY(t->thread_name_cstr), thread_name); t->handle = CreateThread( NULL, SYS_THREAD_STACK_SIZE, win32_thread_proc, t, 0, NULL ); if (!t->handle) { sys_panic(STR("Failed to create thread")); } res = sys_thread_from_win32_thread_locked(&lock, t); } sys_mutex_unlock(&lock); return res; } void sys_thread_wait_release(struct sys_thread *thread) { HANDLE handle = 0; /* Lookup */ struct sys_lock lock = sys_mutex_lock_s(&G.threads_mutex); { struct win32_thread *t = win32_thread_from_sys_thread_locked(&lock, *thread); if (t) { handle = t->handle; } } sys_mutex_unlock(&lock); /* Wait */ if (handle) { DWORD res = WaitForSingleObject(handle, INFINITE); CloseHandle(handle); ASSERT(res != WAIT_FAILED); (UNUSED)res; } } u32 sys_thread_id(void) { return GetCurrentThreadId(); } #if RTC void sys_thread_assert(u32 tid) { ASSERT(sys_thread_id() == tid); } #endif /* ========================== * * Message box * ========================== */ void sys_message_box(enum sys_message_box_kind kind, struct string message) { struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *message_wstr = wstr_from_string(scratch.arena, message); const wchar_t *title = L""; UINT mbox_type = MB_SETFOREGROUND; switch (kind) { case SYS_MESSAGE_BOX_KIND_OK: { mbox_type |= MB_ICONINFORMATION; } break; case SYS_MESSAGE_BOX_KIND_WARNING: { title = L"Warning"; mbox_type |= MB_ICONWARNING; } break; case SYS_MESSAGE_BOX_KIND_ERROR: { title = L"Error"; mbox_type |= MB_ICONERROR; } break; case SYS_MESSAGE_BOX_KIND_FATAL: { title = L"Fatal error"; mbox_type |= MB_ICONSTOP; } break; } logf_info("Showing message box: \"%F\"", FMT_STR(message)); MessageBoxExW(NULL, message_wstr, title, mbox_type, 0); scratch_end(scratch); } /* ========================== * * Clipboard * ========================== */ void sys_set_clipboard_text(struct string str) { if (OpenClipboard(0)) { struct temp_arena scratch = scratch_begin_no_conflict(); struct string16 str16 = string16_from_string(scratch.arena, str); u64 str16_size_bytes = str16.len * 2; EmptyClipboard(); HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, str16_size_bytes + 1); if (handle) { u16 *dest_wstr = (u16 *)GlobalLock(handle); MEMCPY(dest_wstr, str16.text, str16_size_bytes); dest_wstr[str16.len] = 0; GlobalUnlock(handle); SetClipboardData(CF_UNICODETEXT, handle); } CloseClipboard(); scratch_end(scratch); } } struct string sys_get_clipboard_text(struct arena *arena) { struct string res = { 0 }; if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(0)) { HANDLE handle = GetClipboardData(CF_UNICODETEXT); if (handle) { u16 *src_wstr = (u16 *)GlobalLock(handle); res = string_from_string16(arena, string16_from_wstr(src_wstr)); GlobalUnlock(handle); } CloseClipboard(); } return res; } /* ========================== * * Util * ========================== */ u32 sys_num_logical_processors(void) { return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); } void sys_exit(void) { ExitProcess(1); } u32 sys_rand_u32(void) { u32 v; RtlGenRandom(&v, sizeof(v)); return v; } void sys_panic(struct string msg) { ASSERT(false); if (atomic_i32_eval_compare_exchange(&G.panicking, 0, 1) == 0) { log_panic(msg); wchar_t *wstr = G.panic_wstr; u64 wstr_len = 0; wchar_t prefix[] = L"A fatal error has occured and the application needs to exit:\n\n"; MEMCPY(wstr, prefix, min_u64(ARRAY_COUNT(G.panic_wstr), (ARRAY_COUNT(prefix) << 1))); wstr_len += ARRAY_COUNT(prefix) - 1; struct string str8 = msg; u64 pos8 = 0; while (pos8 < str8.len) { struct string str8_remaining = { .len = (str8.len - pos8), .text = str8.text + pos8 }; struct uni_decode_utf8_result decoded = uni_decode_utf8(str8_remaining); struct uni_encode_utf16_result encoded = uni_encode_utf16(decoded.codepoint); u64 wstr_new_len = wstr_len + encoded.count16; if (wstr_new_len < (ARRAY_COUNT(G.panic_wstr) - 1)) { u16 *dest = wstr + wstr_len; MEMCPY(dest, encoded.chars16, (encoded.count16 << 1)); wstr_len = wstr_new_len; pos8 += decoded.advance8; } else { break; } } wstr[wstr_len] = 0; WRITE_BARRIER(); SetEvent(G.panic_event); /* Wait for thread to be terminated */ if (GetCurrentThreadId() != G.main_thread_id) { Sleep(INFINITE); } } } /* ========================== * * Sleep * ========================== */ /* https://blog.bearcats.nl/perfect-sleep-function/ */ INTERNAL void win32_precise_sleep_timer(f64 seconds, HANDLE timer) { __prof; /* TODO: Does the high frequency timer even require setting / scaling of * timeBeginPeriod/scheduler_period_ms? There isn't much documentation. */ i64 qpc_per_second = G.timer_frequency.QuadPart; i32 scheduler_period_ms = G.scheduler_period_ms; LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); INT64 target_qpc = (INT64)(qpc.QuadPart + seconds * qpc_per_second); /* TODO: Maybe increase tolerance for higher precision but more power usage */ //const double tolerance = 0.001200 * scheduler_period_ms; const double tolerance = 0.000520 * scheduler_period_ms; INT64 max_ticks = (INT64)scheduler_period_ms * 9500; while (true) { __profscope(win32_sleep_part); /* Break sleep up into parts that are lower than scheduler period */ double remaining_seconds = (double)(target_qpc - qpc.QuadPart) / (double)qpc_per_second; INT64 sleep_ticks = (INT64)((remaining_seconds - tolerance) * 10000000); if (sleep_ticks <= 0) { break; } LARGE_INTEGER due; due.QuadPart = -(sleep_ticks > max_ticks ? max_ticks : sleep_ticks); SetWaitableTimerEx(timer, &due, 0, NULL, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); QueryPerformanceCounter(&qpc); } /* Spin for any remaining time */ { __profscope(sleep_spin); while (qpc.QuadPart < target_qpc) { YieldProcessor(); QueryPerformanceCounter(&qpc); } } } INTERNAL void win32_precise_sleep_legacy(f64 seconds) { __prof; i64 qpc_per_second = G.timer_frequency.QuadPart; i32 scheduler_period_ms = G.scheduler_period_ms; LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); i64 target_qpc = (i64)(qpc.QuadPart + seconds * qpc_per_second); /* TODO: Calculate tolerance */ /* TODO: Maybe increase tolerance for higher precision but more power usage */ //const double tolerance = 1.02; const double tolerance = 0.52 * scheduler_period_ms; /* Sleep */ f64 sleep_ms = (seconds * 1000) - tolerance; i32 sleep_slices = (i32)(sleep_ms / scheduler_period_ms); if (sleep_slices > 0) { __profscope(win32_sleep); Sleep((DWORD)sleep_slices * scheduler_period_ms); } { __profscope(win32_qpc); QueryPerformanceCounter(&qpc); } /* Spin for any remaining time */ { __profscope(sleep_spin); while (qpc.QuadPart < target_qpc) { YieldProcessor(); QueryPerformanceCounter(&qpc); } } } void sys_sleep_precise(f64 seconds) { __prof; HANDLE timer = win32_thread_get_tls()->sleep_timer; if (timer) { /* Use newer sleeping method */ win32_precise_sleep_timer(seconds, timer); } else { /* Fall back to older sleep method if CREATE_WAITABLE_TIMER_HIGH_RESOLUTION * is not available due to older windows version */ win32_precise_sleep_legacy(seconds); } } void sys_sleep(f64 seconds) { __prof; u32 ms = max_u32(1, math_round_to_int((f32)(seconds * 1000.0))); Sleep(ms); } /* ========================== * * Entry point * ========================== */ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(app_thread_entry_point, arg) { (UNUSED)arg; app_entry_point(); } int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, _In_ LPWSTR command_line, _In_ int show_code) { (UNUSED)instance; (UNUSED)prev_instance; (UNUSED)command_line; (UNUSED)show_code; const wchar_t *error_msg = NULL; /* ========================== * * Win32 setup * ========================== */ G.main_thread_id = GetCurrentThreadId(); SetThreadDescription(GetCurrentThread(), L"Main thread"); /* Set up panic event */ G.panic_event = CreateEventW(NULL, true, false, NULL); /* Query system info */ GetSystemInfo(&G.info); QueryPerformanceFrequency(&G.timer_frequency); QueryPerformanceCounter(&G.timer_start); { TIMECAPS caps; timeGetDevCaps(&caps, sizeof caps); G.scheduler_period_ms = (i32)caps.wPeriodMin; } /* Set up timing period */ timeBeginPeriod(G.scheduler_period_ms); /* Set up condition variables */ G.condition_variables_mutex = sys_mutex_alloc(); G.condition_variables_arena = arena_alloc(GIGABYTE(64)); /* Set up threads */ G.threads_mutex = sys_mutex_alloc(); G.threads_arena = arena_alloc(GIGABYTE(64)); /* Set up windows */ G.windows_mutex = sys_mutex_alloc(); G.windows_arena = arena_alloc(GIGABYTE(64)); /* Set up TLS index */ G.thread_tls_index = TlsAlloc(); if (G.thread_tls_index == TLS_OUT_OF_INDEXES) { /* TODO: GetLastError */ error_msg = L"Platform initialization error: TLS_OUT_OF_INDEXES"; goto abort; } /* Initialize vk table */ win32_init_vk_btn_table(); /* Create window class */ { /* Register the window class */ WNDCLASSEXW *wc = &G.window_class; wc->cbSize = sizeof(WNDCLASSEX); wc->lpszClassName = WINDOW_CLASS_NAME; wc->hCursor = LoadCursor(NULL, IDC_ARROW); wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; //wc->hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc->lpfnWndProc = win32_window_proc; wc->hInstance = instance; /* Use first icon resource as window icon (same as explorer) */ wchar_t path[4096] = { 0 }; GetModuleFileNameW(instance, path, ARRAY_COUNT(path)); ExtractIconExW(path, 0, &wc->hIcon, &wc->hIconSm, 1); if (!RegisterClassExW(wc)) { /* TODO: GetLastError */ error_msg = L"Failed to register window class"; goto abort; } } /* Register raw input */ { RAWINPUTDEVICE rid = (RAWINPUTDEVICE) { .usUsagePage = 0x01, /* HID_USAGE_PAGE_GENERIC */ .usUsage = 0x02, /* HID_USAGE_GENERIC_MOUSE */ //.dwFlags = RIDEV_NOLEGACY /* Adds mouse and also ignores legacy mouse messages */ }; if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { /* TODO: GetLastError */ error_msg = L"Failed to register raw input device"; goto abort; } } /* ========================== * * App thread setup * ========================== */ /* Initialize main thread context. */ struct win32_tls main_thread_tls = win32_tls_alloc(); win32_thread_set_tls(&main_thread_tls); /* Call app thread and wait for return */ { /* Start app thread */ struct sys_thread app_thread = sys_thread_alloc(&app_thread_entry_point, NULL, STR("[P9] App thread")); /* Get app thread handle */ HANDLE app_thread_handle = 0; struct sys_lock lock = sys_mutex_lock_s(&G.threads_mutex); { struct win32_thread *wt = win32_thread_from_sys_thread_locked(&lock, app_thread); app_thread_handle = wt->handle; } sys_mutex_unlock(&lock); /* Wait for either app thread exit or panic */ if (app_thread_handle) { HANDLE wait_handles[] = { app_thread_handle, G.panic_event }; DWORD res = WaitForMultipleObjects(ARRAY_COUNT(wait_handles), wait_handles, false, INFINITE); ASSERT(res != WAIT_FAILED); (UNUSED)res; } } /* Check if panicking */ if (atomic_i32_eval(&G.panicking)) { /* Wait for panic message to be ready */ WaitForSingleObject(G.panic_event, INFINITE); /* Force stop threads */ for (struct win32_thread *t = G.threads_last; t; t = t->prev) { HANDLE handle = t->handle; TerminateThread(handle, 0); CloseHandle(handle); } /* Set error and abort */ error_msg = G.panic_wstr; goto abort; } /* Release main thread context */ win32_tls_release(&main_thread_tls); /* ========================== * * Abort * ========================== */ abort: if (error_msg) { MessageBoxExW(NULL, error_msg, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND, 0); ASSERT(false); return 1; } return 0; } /* ========================== * * CRT Stub * ========================== */ #if !CRTLIB #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-variable-declarations" #pragma clang diagnostic ignored "-Wmissing-prototypes" /* Enable floating point */ __attribute((used)) int _fltused; __attribute((used)) void __stdcall wWinMainCRTStartup(void) { int result = wWinMain(GetModuleHandle(0), 0, GetCommandLineW(), 0); ExitProcess(result); } #pragma clang diagnostic pop #endif /* !CRTLIB */