// #TODO: #OS_Win32 // [ ] #Thread cleanup: in `thread_deinit` is there any requirement to cleanup child threads? // [ ] #Exception handling code in `Win32_Exception_Filter` // [ ] #cpuid - enumerate CPUs and Thread count (current implementation doesn't work) #if OS_WINDOWS constexpr s64 FILETIME_TO_UNIX = 116444736000000000i64; f64 GetUnixTimestamp () { FILETIME fileTime; GetSystemTimePreciseAsFileTime(&fileTime); s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime; return (ticks - FILETIME_TO_UNIX) / (10.0 * 1000.0 * 1000.0); } s64 GetUnixTimestampNanoseconds () { FILETIME fileTime; GetSystemTimePreciseAsFileTime(&fileTime); s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime; // in 100ns ticks s64 unix_time = (ticks - FILETIME_TO_UNIX); // in 100ns ticks s64 unix_time_nanoseconds = unix_time * 100; return unix_time_nanoseconds; } #endif struct OS_System_Info { s32 logical_processor_count; s32 physical_core_count; s32 primary_core_count; s32 secondary_core_count; // Any weaker or "Efficiency" cores. u64 page_size; u64 large_page_size; u64 allocation_granularity; string machine_name; // Monitor stuff: b32 monitors_enumerated; Array monitors; // Back with GPAllocator }; struct OS_Process_Info { u32 process_id; b32 large_pages_allowed; string binary_path; string working_path; string user_program_data_path; Array module_load_paths; Array environment_paths; b32 window_class_initialized; Arena* event_arena; Array windows; }; struct OS_State_Win32 { Arena* arena; OS_System_Info system_info; OS_Process_Info process_info; }; global OS_State_Win32 global_win32_state; internal b32 global_win32_is_quiet = 0; // No console output internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) { if (global_win32_is_quiet) { ExitProcess(1); } local_persist volatile LONG first = 0; if(InterlockedCompareExchange(&first, 1, 0) != 0) { // prevent failures in other threads to popup same message box // this handler just shows first thread that crashes // we are terminating afterwards anyway for (;;) Sleep(1000); } // #Exception handling code (TODO) ExitProcess(1); return 0; } // internal void Main_Entry_Point (int argc, WCHAR **argv); internal void Win32_Entry_Point (int argc, WCHAR **argv) { // Timed_Block_Print("Win32_Entry_Point"); // See: w32_entry_point_caller(); (raddebugger) SetUnhandledExceptionFilter(&Win32_Exception_Filter); SYSTEM_INFO sysinfo = {0}; GetSystemInfo(&sysinfo); // Try to allow large pages if we can. // b32 large_pages_allowed = 0; // { // HANDLE token; // if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) // { // LUID luid; // if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) // { // TOKEN_PRIVILEGES priv; // priv.PrivilegeCount = 1; // priv.Privileges[0].Luid = luid; // priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); // } // CloseHandle(token); // } // } push_arena(thread_context()->arena); { OS_System_Info* info = &global_win32_state.system_info; info->logical_processor_count = (s32)sysinfo.dwNumberOfProcessors; info->page_size = sysinfo.dwPageSize; info->large_page_size = GetLargePageMinimum(); info->allocation_granularity = sysinfo.dwAllocationGranularity; } { OS_Process_Info* info = &global_win32_state.process_info; info->large_pages_allowed = false; info->process_id = GetCurrentProcessId(); } // #cpuid /*{ OS_System_Info* info = &global_win32_state.system_info; // [ ] Extract input args u32 length; GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, (PDWORD)&length); u8* cpu_information_buffer = NewArray(length); GetLogicalProcessorInformationEx(RelationProcessorCore, // *sigh* (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)cpu_information_buffer, (PDWORD)&length); u32 offset = 0; u32 all_cpus_count = 0; u32 max_performance = 0; u32 performance_core_count = 0; // u32 efficient_core_count; while (offset < length) { SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* cpu_information = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(cpu_information_buffer + offset); offset += cpu_information->Size; u32 count_per_group_physical = 1; u32 value = (u32)cpu_information->Processor.GroupMask->Mask; u32 count_per_group = __popcnt(value); // logical if (cpu_information->Relationship != RelationProcessorCore) continue; if (cpu_information->Processor.EfficiencyClass > max_performance) { max_performance = cpu_information->Processor.EfficiencyClass; performance_core_count = count_per_group_physical; } else if (cpu_information->Processor.EfficiencyClass == max_performance) { performance_core_count += count_per_group_physical; } all_cpus_count += count_per_group; } info->physical_core_count = (s32)all_cpus_count; info->primary_core_count = (s32)performance_core_count; } // info->secondary_core_count = ; */ { OS_System_Info* info = &global_win32_state.system_info; info->monitors.allocator = GPAllocator(); u8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; DWORD size = MAX_COMPUTERNAME_LENGTH + 1; if(GetComputerNameA((char*)buffer, &size)) { string machine_name_temp = string(size, buffer); info->machine_name = copy_string(machine_name_temp); } } { OS_Process_Info* info = &global_win32_state.process_info; info->windows.allocator = GPAllocator(); DWORD length = GetCurrentDirectoryW(0, 0); // This can be freed later when we call temp_reset(); u16* memory = NewArray(temp(), length + 1); length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); info->working_path = wide_to_utf8(memory, length); Assert(is_valid(info->working_path)); } // Setup event arena, allocators for Array<> types. if (!global_win32_state.process_info.event_arena) { global_win32_state.process_info.event_arena = next_arena(Arena_Reserve::Size_64K); } // [ ] Get Working directory (info->working_path) // [ ] GetEnvironmentStringsW temp_reset(); printf("Hello there!\n\n"); } C_LINKAGE DWORD OS_Windows_Thread_Entry_Point (void* parameter) { Thread* thread = (Thread*)parameter; set_thread_context(thread->context); DWORD result = (DWORD)thread->proc(thread); return result; } // Individual Thread API internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name="") { Assert(thread != nullptr && proc != nullptr); DWORD windows_thread_id = 0; HANDLE windows_thread = CreateThread(nullptr, 0, OS_Windows_Thread_Entry_Point, thread, CREATE_SUSPENDED, &windows_thread_id); if (windows_thread == 0 || windows_thread == INVALID_HANDLE_VALUE) { return false; } s64 this_thread_index = InterlockedIncrement(&next_thread_index); // #NewContext ExpandableArena* arena_ex = expandable_arena_new(Arena_Reserve::Size_64M, 16); thread->context = New(allocator(arena_ex)); thread->context->temp = expandable_arena_new(Arena_Reserve::Size_2M, 16); thread->context->arena = arena_ex; thread->context->allocator = allocator(arena_ex); thread->context->thread_idx = (s32)this_thread_index; thread->context->thread_name = copy_string(thread_name); thread->context->log_builder = new_string_builder(Arena_Reserve::Size_64M); thread->os_thread.windows_thread = windows_thread; thread->os_thread.windows_thread_id = windows_thread_id; thread->proc = proc; thread->index = this_thread_index; return true; } internal void thread_deinit (Thread* thread) { if (thread->os_thread.windows_thread) { CloseHandle(thread->os_thread.windows_thread); } thread->os_thread.windows_thread = nullptr; arena_delete(thread->context->temp); arena_delete(thread->context->arena); free_string_builder(thread->context->log_builder); // memset(thread, 0xCD, sizeof(Thread); } internal void thread_start (Thread* thread) { ResumeThread(thread->os_thread.windows_thread); } internal bool thread_is_done (Thread* thread, s32 milliseconds=0) { Assert(milliseconds >= -1); DWORD result = WaitForSingleObject(thread->os_thread.windows_thread, (DWORD)milliseconds); return result != WAIT_TIMEOUT; } // #thread_primitives internal void mutex_init (Mutex* mutex) { InitializeCriticalSection(&mutex->csection); } internal void mutex_destroy (Mutex* mutex) { DeleteCriticalSection(&mutex->csection); } internal void lock (Mutex* mutex) { EnterCriticalSection(&mutex->csection); } internal void unlock (Mutex* mutex) { LeaveCriticalSection(&mutex->csection); } internal void semaphore_init (Semaphore* sem, s32 initial_value) { Assert(initial_value >= 0); sem->event = CreateSemaphoreW(nullptr, initial_value, 0x7fffffff, nullptr); } internal void semaphore_destroy (Semaphore* sem) { CloseHandle(sem->event); } internal void signal (Semaphore* sem) { ReleaseSemaphore(sem->event, 1, nullptr); } internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds) { DWORD res = 0; if (milliseconds < 0) { res = WaitForSingleObject(sem->event, INFINITE); } else { res = WaitForSingleObject(sem->event, (u32)milliseconds); } Assert(res != WAIT_FAILED); if (res == WAIT_OBJECT_0) return Wait_For_Result::SUCCESS; if (res == WAIT_TIMEOUT) return Wait_For_Result::TIMEOUT; return Wait_For_Result::ERROR; } internal void condition_variable_init (Condition_Variable* cv) { InitializeConditionVariable(&cv->condition_variable); } internal void condition_variable_destroy (Condition_Variable* cv) { // No action required. } internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms) { SleepConditionVariableCS(&cv->condition_variable, &mutex->csection, (DWORD)wait_time_ms); } internal void wake (Condition_Variable* cv) { WakeConditionVariable(&cv->condition_variable); } internal void wake_all (Condition_Variable* cv) { WakeAllConditionVariable(&cv->condition_variable); } internal string get_error_string (OS_Error_Code error_code) { u16* lpMsgBuf; bool success = (bool)FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPWSTR)&lpMsgBuf, 0, nullptr); if (!success) { return ""; } push_allocator(temp()); string result = wide_to_utf8(lpMsgBuf); LocalFree(lpMsgBuf); return result; // trim_right(result, "\r\n"); } internal bool file_is_valid (File file) { if (file.handle == INVALID_HANDLE_VALUE) return false; if (file.handle == 0) return false; return true; } internal File file_open (string file_path, bool for_writing, bool keep_existing_content, bool log_errors) { HANDLE handle; push_allocator(temp()); // for utf8 -> wide conversions: if (for_writing) { u32 creation = (keep_existing_content) ? OPEN_ALWAYS : CREATE_ALWAYS; handle = CreateFileW( (LPCWSTR)utf8_to_wide(file_path).data, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ, nullptr, creation, 0, nullptr); } else { u32 creation = OPEN_EXISTING; handle = CreateFileW( (LPCWSTR)utf8_to_wide(file_path).data, FILE_GENERIC_READ, FILE_SHARE_READ, nullptr, creation, 0, nullptr); } if (handle == INVALID_HANDLE_VALUE && log_errors) { OS_Error_Code error_code = GetLastError(); log_error("Could not open file `%s`, code: %d, %s", file_path, error_code, get_error_string(error_code).data); } File file; file.handle = handle; return file; } internal void file_close (File* file) { CloseHandle(file->handle); } internal bool file_read (File file, u8* data, s64 bytes_to_read_count, s64* bytes_read_count) { // ignore bytes_read_count if null. if (data == nullptr) { log_error("file_read called with null destination pointer.\n"); if (bytes_read_count) (*bytes_read_count) = 0; return false; } if (bytes_to_read_count <= 0) { if (bytes_read_count) (*bytes_read_count) = 0; return false; } bool read_success = false; s64 total_read = 0; // loop to read more data than can be specified by the DWORD param ReadFile takes: while (total_read < bytes_to_read_count) { s64 remaining = bytes_to_read_count - total_read; DWORD to_read; if (remaining <= 0x7FFFFFFF) { to_read = (DWORD)remaining; } else { to_read = 0x7FFFFFFF; // 2147483647 bytes ~2GB } DWORD single_read_length = 0; read_success = (bool)ReadFile(file.handle, data + total_read, to_read, &single_read_length, nullptr); total_read += single_read_length; if (!read_success || single_read_length == 0) { break; } } if (bytes_read_count) (*bytes_read_count) = total_read; return read_success; } internal bool file_length (File file, s64* length) { if (length == nullptr) { log_error("Calling file_length with null `length` param!"); return false; } if (!file_is_valid(file)) { return false; } s64 size; bool success = (bool)GetFileSizeEx(file.handle, (PLARGE_INTEGER)&size); (*length) = size; return true; } internal bool file_length (string file_path, s64* length) { if (length == nullptr) { log_error("Calling file_length with null `length` param!"); return false; } File f = file_open(file_path); if (!file_is_valid(f)) { return false; } bool success = file_length(f, length); return success; } internal s64 file_current_position (File file) { constexpr s64 invalid_file_position = -1; if (!file_is_valid(file)) { return invalid_file_position; } s64 offset = 0; LARGE_INTEGER liDistanceToMove; bool result = (bool)SetFilePointerEx(file.handle, liDistanceToMove, (PLARGE_INTEGER)&offset, FILE_CURRENT); if (!result) { return invalid_file_position; } return (s64)offset; } internal bool file_set_position (File file, s64 position) { if (!file_is_valid(file)) { return false; } if (position < 0) { Assert(false); return false; } LARGE_INTEGER position_li; position_li.QuadPart = position; return (bool)SetFilePointerEx(file.handle, position_li, nullptr, FILE_BEGIN); } internal ArrayView read_entire_file (File file) { ArrayView file_data; bool result = file_length(file, &file_data.count); if (!result) return {}; result = file_set_position(file, 0); if (!result) return {}; file_data.data = NewArray(file_data.count, false); if (file_data.data == nullptr) return {}; s64 bytes_read = 0; result = file_read(file, file_data.data, file_data.count, &bytes_read); if (!result) { array_free(file_data); return {}; } Assert(bytes_read == file_data.count); file_data.count = bytes_read; return file_data; } internal ArrayView read_entire_file (string file_path, bool log_errors) { File f = file_open(file_path, false, false, log_errors); if (!file_is_valid(f)) return {}; ArrayView file_data = read_entire_file(f); file_close(&f); return file_data; } internal bool file_write (File* file, void* data, s64 length) { // @incomplete - deal with inputs > 32 bits (>2GB) u32 length_u32 = (u32)length; Assert(length == length_u32); u32 bytes_written; bool result = (bool)WriteFile(file->handle, data, length_u32, (LPDWORD)&bytes_written, nullptr); return result; } internal bool write_entire_file (string file_path, void* file_data, s64 count) { File f = file_open(file_path, true, false); if (!file_is_valid(f)) return false; bool result = file_write(&f, file_data, count); file_close(&f); return result; } internal bool write_entire_file (string file_path, ArrayView file_data) { return write_entire_file(file_path, file_data.data, file_data.count); } internal LRESULT // see os_w32_wnd_proc from raddebugger. win32_wnd_proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; bool good = true; Arena* event_arena = global_win32_state.process_info.event_arena; Assert(event_arena != nullptr); switch (uMsg) { default: { result = DefWindowProcW(hwnd, uMsg, wParam, lParam); } break; case WM_CLOSE: { ExitProcess(0); // #temp. } break; } return result; } internal BOOL monitor_enum_proc (HMONITOR hMonitor, HDC hdc, RECT* rect, LPARAM data) { Monitor monitor = {}; monitor.left = rect->left; monitor.top = rect->top; monitor.right = rect->right; monitor.bottom = rect->bottom; monitor.monitor_info.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(hMonitor, &monitor.monitor_info); monitor.primary = !!(monitor.monitor_info.dwFlags & 0x1); monitor.present = true; array_add(global_win32_state.system_info.monitors, monitor); return true; } // #TODO: how do I setup a callback if monitors configuration is changed? // we handle WM_DISPLAYCHANGE or WM_DEVICECHANGE and call EnumDisplayMonitors internal void os_enumerate_monitors () { if (!global_win32_state.system_info.monitors_enumerated) { // should reset array? if (!EnumDisplayMonitors(nullptr, nullptr, monitor_enum_proc, 0)) { log_error("Failed to enumerate monitors\n"); Assert(false); // Failed to enumerate monitors ExitProcess(1); } global_win32_state.system_info.monitors_enumerated = true; } } Window_Dimensions platform_get_centered_window_dimensions (bool open_on_largest_monitor=false) { os_enumerate_monitors(); Assert(global_win32_state.system_info.monitors.count > 0); // must have at least 1 monitor! Array monitors = global_win32_state.system_info.monitors; Monitor monitor = monitors[0]; if (open_on_largest_monitor) { s64 max_area = 0; for (s64 i = 0; i < monitors.count; i += 1) { s64 width = monitors[i].right - monitors[i].left; s64 height = monitors[i].bottom - monitors[i].top; s64 area = width * height; if (max_area < area) { monitor = monitors[i]; max_area = area; } else if (max_area == area && monitors[i].primary) { // if monitors are the same dimension, then just use primary monitor monitor = monitors[i]; } } } else { // Opens on whatever monitor is marked as "primary." for (s64 i = 0; i < monitors.count; i += 1) { if (monitors[i].primary) { monitor = monitors[i]; break; } } } s64 monitor_width = monitor.right - monitor.left; s64 monitor_height = monitor.bottom - monitor.top; s64 window_width = (s64)((f64)monitor_width / 1.125); s64 window_height = (s64)((f64)monitor_height / 1.25); s64 window_x = (monitor.left + (monitor_width / 2) - (window_width / 2)); s64 window_y = (monitor.top + (monitor_height / 2) - (window_height / 2)); Window_Dimensions dimensions = { (s32)window_x, (s32)window_y, (s32)window_width, (s32)window_height, }; return dimensions; } // #window_creation -> put API in OS_Win32.h // Instead of returning WindowType, return the handle + other information. bool os_create_window (string new_window_name, Window_Type parent, bool center_window, bool open_on_largest_monitor, bool display_window) { local_persist string class_name = "Win32_Window_Class"; if (!global_win32_state.process_info.window_class_initialized) { global_win32_state.process_info.window_class_initialized = true; WNDCLASSEXW wc = {}; wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = win32_wnd_proc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = nullptr; wc.hIcon = (HICON)LoadImageW(nullptr, (LPCWSTR)utf8_to_wide("tmp.ico").data, IMAGE_ICON, 0, 0, LR_LOADFROMFILE); wc.hCursor = LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW); wc.hbrBackground = nullptr; wc.lpszMenuName = nullptr; wc.lpszClassName = (LPCWSTR)utf8_to_wide(class_name).data; if (RegisterClassExW(&wc) == 0) { log_error("RegisterClassExW Failed."); return false; } } Window_Dimensions wd = { 100, 100, 640, 480 }; if (center_window) { wd = platform_get_centered_window_dimensions(open_on_largest_monitor); log("[Window_Dimensions] location: (%d, %d); size: %dx%d px", wd.window_x, wd.window_y, wd.window_width, wd.window_height); } HWND hwnd = CreateWindowExW( WS_EX_APPWINDOW, (LPCWSTR)utf8_to_wide(class_name).data, (LPCWSTR)utf8_to_wide(new_window_name).data, WS_OVERLAPPEDWINDOW, wd.window_x, wd.window_y, wd.window_width, wd.window_height, nullptr, nullptr, nullptr, nullptr ); if (!hwnd) { Assert(false); DestroyWindow(hwnd); return false; } // Display the window: if (display_window) { UpdateWindow(hwnd); ShowWindow(hwnd, SW_SHOW); } // Save window to stack Window_Info info = {}; info.window = hwnd; info.initial_dimensions = wd; info.is_main_window = (parent == nullptr); // Should we mutex this? Seems very unlikely we'll ever need to call this // from multiple threads at the same time. array_add(global_win32_state.process_info.windows, info); return true; } Window_Info get_main_window () { Array windows = global_win32_state.process_info.windows; Assert(windows.count > 0); for (s64 i = 0; i < windows.count; i += 1) { if (windows[i].is_main_window) { return windows[i]; } } return {}; } bool get_window_dimensions(Window_Info* info, s32* width, s32* height) { if (info == nullptr || width == nullptr || height == nullptr) return false; Assert(width && height); RECT c = {}; bool success = GetClientRect(info->window, &c); if (!success) { (*width) = 0; (*height) = 0; return false; } (*width) = (s32)(c.right - c.left); (*height) = (s32)(c.bottom - c.top); return true; } // #TODO: #window_creation // [ ] resize_window // [ ] position_window // [ ] toggle_fullscreen // [ ] get_dimensions // #TODO: #window_interaction (mouse/keyboard) // [ ] get_mouse_pointer_position // [ ] ... What APIs do I need for Keyboard