#include "core/core_inc.h" #include "app.gen.h" #include "core/core_inc.c" #include "app.gen.c" #include "app_win32_opengl.c" #pragma comment(linker, "/subsystem:windows") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "winmm.lib") gb b32 w32_good_scheduling; gb WNDCLASSW w32_wc; gb HWND w32_window_handle; gb HDC w32_dc; gb b32 w32_quit_app; fn v2f64_t w32_get_window_size(HWND window) { RECT window_rect; GetClientRect(window, &window_rect); f64 x = window_rect.right - window_rect.left; f64 y = window_rect.bottom - window_rect.top; return (v2f64_t){x, y}; } fn v2f64_t w32_get_mouse_pos(HWND window) { POINT p; GetCursorPos(&p); ScreenToClient(window, &p); return (v2f64_t){p.x, p.y}; } fn f64 w32_get_dpr(HWND window_handle) { UINT dpi = GetDpiForWindow(window_handle); if (dpi == 0) { return 1.0; } f64 result = (f64)dpi / 96.0; return result; } fn f64 w32_seconds_now(void) { static int64_t counts_per_second; if (counts_per_second == 0) { LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); counts_per_second = freq.QuadPart; } LARGE_INTEGER time; QueryPerformanceCounter(&time); f64 result = (f64)time.QuadPart / (f64)counts_per_second; return result; } /////////////////////////////// // event processing gb app_event_list_t w32_event_list; gb ma_arena_t *w32_event_arena; fn void w32_push_event(app_event_t event) { app_event_t *ev = ma_push_type(w32_event_arena, app_event_t); *ev = event; if (GetKeyState(VK_CONTROL) & 0x8000) ev->ctrl = true; if (GetKeyState(VK_SHIFT) & 0x8000) ev->shift = true; if (GetKeyState(VK_MENU) & 0x8000) ev->alt = true; ev->window_size = w32_get_window_size(w32_window_handle); ev->mouse_pos = w32_get_mouse_pos(w32_window_handle); ev->dpr = w32_get_dpr(w32_window_handle); SLLQ_APPEND(w32_event_list.first, w32_event_list.last, ev); w32_event_list.len += 1; } fn void w32_push_wm_char_event(WPARAM wparam) { if (wparam >= 32 || wparam == 127) return; s8_t string = s8_lit("?"); utf32_result_t encode = utf16_to_utf32((u16 *)&wparam, 1); if (!encode.error) { utf8_result_t encode8 = utf32_to_utf8(encode.out_str); if (!encode8.error) { string = s8(encode8.out_str, encode8.len); string = s8_copy(w32_event_arena, string); } } w32_push_event((app_event_t){ .kind = app_event_kind_text, .text = string, }); } fn LRESULT CALLBACK w32_window_proc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_CLOSE: { ExitProcess(0); } break; case WM_KEYDOWN: { w32_push_event((app_event_t){ .kind = app_event_kind_key_down, .key = w32_map_wparam_to_app_key(wparam) }); } break; case WM_KEYUP: { w32_push_event((app_event_t){ .kind = app_event_kind_key_up, .key = w32_map_wparam_to_app_key(wparam) }); } break; case WM_LBUTTONDOWN: { SetCapture(wnd); w32_push_event((app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = app_mouse_button_left, }); } break; case WM_LBUTTONUP: { ReleaseCapture(); w32_push_event((app_event_t){ .kind = app_event_kind_mouse_up, .mouse_button = app_mouse_button_left, }); } break; case WM_RBUTTONDOWN: { w32_push_event((app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = app_mouse_button_right, }); } break; case WM_RBUTTONUP: { w32_push_event((app_event_t){ .kind = app_event_kind_mouse_up, .mouse_button = app_mouse_button_right, }); } break; case WM_MBUTTONDOWN: { SetCapture(wnd); w32_push_event((app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = app_mouse_button_middle, }); } break; case WM_MBUTTONUP: { ReleaseCapture(); w32_push_event((app_event_t){ .kind = app_event_kind_mouse_up, .mouse_button = app_mouse_button_middle, }); } break; case WM_MOUSEWHEEL: { int zDelta = GET_WHEEL_DELTA_WPARAM(wparam); w32_push_event((app_event_t){ .kind = app_event_kind_mouse_wheel, .mouse_wheel_delta = (v3f64_t){0, zDelta}, }); } break; // @todo: // WM_CHAR sends 2byte codepoints in 2 messages?????? case WM_CHAR: { w32_push_wm_char_event(wparam); } break; default: return DefWindowProcW(wnd, msg, wparam, lparam); } return 0; } app_event_list_t w32_get_events(ma_arena_t *arena, b32 wait) { w32_event_arena = arena; zero_struct(&w32_event_list); MSG msg; if (wait) { GetMessageW(&msg, NULL, 0, 0); TranslateMessage(&msg); DispatchMessageW(&msg); } while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return w32_event_list; } /////////////////////////////// // canvas functions typedef struct w32_canvas_t w32_canvas_t; struct w32_canvas_t { u32 *memory; HWND window_handle; HBITMAP dib; HDC dib_dc; v2f64_t window_size; }; w32_canvas_t w32_create_canvas(HWND window_handle) { v2f64_t window_size = w32_get_window_size(window_handle); w32_canvas_t result = {.window_handle = window_handle, .window_size = window_size}; HDC window_dc = GetDC(window_handle); BITMAPINFO bminfo = {0}; { bminfo.bmiHeader.biSize = sizeof(bminfo.bmiHeader); bminfo.bmiHeader.biWidth = (LONG)window_size.x; bminfo.bmiHeader.biHeight = (LONG)window_size.y; bminfo.bmiHeader.biPlanes = 1; bminfo.bmiHeader.biBitCount = 32; bminfo.bmiHeader.biCompression = BI_RGB; // AA RR GG BB bminfo.bmiHeader.biXPelsPerMeter = 1; bminfo.bmiHeader.biYPelsPerMeter = 1; } result.dib = CreateDIBSection(window_dc, &bminfo, DIB_RGB_COLORS, (void **)&result.memory, 0, 0); result.dib_dc = CreateCompatibleDC(window_dc); return result; } void w32_destroy_canvas(w32_canvas_t *canvas) { if (canvas->memory) { DeleteDC(canvas->dib_dc); DeleteObject(canvas->dib); memory_zero(canvas, sizeof(*canvas)); } } void w32_present_canvas(w32_canvas_t *canvas) { if (canvas->memory) { SelectObject(canvas->dib_dc, canvas->dib); HDC window_dc = GetDC(canvas->window_handle); BitBlt(window_dc, 0, 0, (int)canvas->window_size.x, (int)canvas->window_size.y, canvas->dib_dc, 0, 0, SRCCOPY); } } void w32_try_resizing_canvas(w32_canvas_t *canvas, HWND window_handle) { v2f64_t window_size = w32_get_window_size(window_handle); if (canvas->window_size.x != window_size.x || canvas->window_size.y != window_size.y) { w32_destroy_canvas(canvas); *canvas = w32_create_canvas(window_handle); } } int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { tcx.temp = ma_create(ma_default_reserve_size); // Set DPI aware, @todo: verify / new way to do this | @todo: get dpi ratio { typedef enum W32_PROCESS_DPI_AWARENESS { W32_PROCESS_DPI_UNAWARE = 0, W32_PROCESS_SYSTEM_DPI_AWARE = 1, W32_PROCESS_PER_MONITOR_DPI_AWARE = 2 } W32_PROCESS_DPI_AWARENESS; typedef HRESULT MU_SetProcessDpiAwareness(W32_PROCESS_DPI_AWARENESS); HMODULE shcore = LoadLibraryA("Shcore.dll"); if (shcore) { MU_SetProcessDpiAwareness *set_dpi_awr = (MU_SetProcessDpiAwareness *)GetProcAddress(shcore, "SetProcessDpiAwareness"); if (set_dpi_awr) { HRESULT hr = set_dpi_awr(W32_PROCESS_PER_MONITOR_DPI_AWARE); assert(SUCCEEDED(hr) && "Failed to set dpi awareness"); } } } // setup better scheduling, @todo: should we do this? { typedef unsigned MU_TimeBeginPeriod(unsigned); HMODULE winmm = LoadLibraryA("winmm.dll"); if (winmm) { MU_TimeBeginPeriod *timeBeginPeriod = (MU_TimeBeginPeriod *)GetProcAddress(winmm, "timeBeginPeriod"); if (timeBeginPeriod) { if (timeBeginPeriod(1) == 0) { w32_good_scheduling = true; } } } } // create window { WNDCLASSW wc = {0}; { wc.lpfnWndProc = w32_window_proc; wc.hInstance = GetModuleHandleW(NULL); wc.lpszClassName = L"HelloClassName"; wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hIcon = NULL; // LoadIcon(wc.hInstance, IDI_APPLICATION); wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; ATOM result = RegisterClassW(&wc); assert(result != 0); w32_wc = wc; } f64 primary_monitor_size_x = (f64)GetSystemMetrics(SM_CXSCREEN); f64 primary_monitor_size_y = (f64)GetSystemMetrics(SM_CYSCREEN); RECT window_rect = {0}; { window_rect.left = 0 + 0; window_rect.right = 0 + (int)(primary_monitor_size_x * 0.8); window_rect.bottom = 30 + (int)(primary_monitor_size_y * 0.8); window_rect.top = 30 + 0; AdjustWindowRectEx(&window_rect, WS_OVERLAPPEDWINDOW, false, 0); } w32_window_handle = CreateWindowW(w32_wc.lpszClassName, L"Zzz... Window, hello!", WS_OVERLAPPEDWINDOW, window_rect.left, window_rect.top, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, NULL, NULL, hInstance, NULL); assert(w32_window_handle); w32_dc = GetDC(w32_window_handle); assert(w32_dc); b32 ok = w32_load_wgl_fns(); assert(ok); ok = w32_create_opengl_context(w32_dc, 4, 5); assert(ok); ShowWindow(w32_window_handle, SW_SHOW); } // w32_canvas_t canvas = w32_create_canvas(w32_window_handle); f64 refresh_rate = 60; DEVMODEW devmodew = {0}; if (EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew)) { refresh_rate = (f64)devmodew.dmDisplayFrequency; } f64 time_frame_start = w32_seconds_now(); f64 time_delta = 1.0 / refresh_rate; f64 time_total = 0.0; f64 time_update = 0.0; u64 consecutive_missed_frames = 0; u64 missed_frames = 0; u64 frame = 0; for (;;) { app_event_list_t event_list = w32_get_events(tcx.temp, false); for (app_event_t *ev = event_list.first; ev; ev = ev->next) { if (ev->kind == app_event_kind_key_down && ev->key == app_key_escape) { ExitProcess(0); } } // w32_try_resizing_canvas(&canvas, w32_window_handle); // debugf("time_update: %f", time_update); #if 0 for (i32 y = 0; y < canvas.window_size.y; y++) { for (i32 x = 0; x < canvas.window_size.x; x++) { canvas.memory[x + y * (int)canvas.window_size.x] = 0xFFFF0000; } } #endif // w32_present_canvas(&canvas); ma_set0(tcx.temp); /////////////////////////////// // end of frame timings f64 time_update_partial = w32_seconds_now() - time_frame_start; time_update = time_update_partial; if (time_update < time_delta) { consecutive_missed_frames = 0; // @todo: we are currently busy looping when we don't get the good schduling // is that actually a good tactic?? if (w32_good_scheduling) { f64 time_to_sleep = time_delta - time_update; f64 time_to_sleep_in_ms = (time_to_sleep * 1000.0) - 1.0; if (time_to_sleep > 0.0) { DWORD ms = (DWORD)time_to_sleep_in_ms; if (ms) { Sleep(ms); } } } // busy loop if we dont have good scheduling // or we woke up early time_update = w32_seconds_now() - time_frame_start; while (time_update < time_delta) { time_update = w32_seconds_now() - time_frame_start; } } else { missed_frames += 1; consecutive_missed_frames += 1; } frame += 1; // @todo: // should this be time_delta or time_update ???? // probably want the locked frame rate and total should reflect that, so choosing // time_delta seems the correct choice but not really sure what is correct. time_total += time_delta; time_frame_start = w32_seconds_now(); } return 0; }