#include "app_win32_opengl.c" #include "glad/glad.h" #include "glad/glad.c" #pragma comment(linker, "/subsystem:windows") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "winmm.lib") fn void app_init(void); fn b32 app_update(app_frame_t *frame); gb b32 w32_good_scheduling; gb WNDCLASSW w32_wc; gb HWND w32_window_handle; gb HDC w32_dc; gb b32 w32_quit_app; fn v2f32_t w32_get_window_size(HWND window) { RECT window_rect; GetClientRect(window, &window_rect); f32 x = (f32)(window_rect.right - window_rect.left); f32 y = (f32)(window_rect.bottom - window_rect.top); return (v2f32_t){x, y}; } fn v2f32_t w32_get_mouse_pos(HWND window) { POINT p; GetCursorPos(&p); ScreenToClient(window, &p); return (v2f32_t){(f32)p.x, (f32)p.y}; } fn f32 w32_get_dpr(HWND window_handle) { UINT dpi = GetDpiForWindow(window_handle); if (dpi == 0) { return 1.0; } f32 result = (f32)dpi / 96.0f; 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_frame_t *w32_frame; gb ma_arena_t *w32_event_arena; gb u64 w32_event_id; fn void w32_push_event(app_frame_t *frame, app_event_t event) { app_event_t *ev = ma_push_type(w32_event_arena, app_event_t); *ev = event; ev->id = ++w32_event_id; ev->mouse_pos = w32_get_mouse_pos(w32_window_handle); if (GetKeyState(VK_CONTROL) & 0x8000) ev->ctrl = true; if (GetKeyState(VK_SHIFT) & 0x8000) ev->shift = true; if (GetKeyState(VK_MENU) & 0x8000) ev->alt = true; SLLQ_APPEND(frame->first_event, frame->last_event, ev); frame->event_count += 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(w32_frame, (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(w32_frame, (app_event_t){ .kind = app_event_kind_key_down, .key = w32_map_wparam_to_app_key(wparam) }); } break; case WM_KEYUP: { w32_push_event(w32_frame, (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(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = app_mouse_button_left, }); } break; case WM_LBUTTONUP: { ReleaseCapture(); w32_push_event(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_up, .mouse_button = app_mouse_button_left, }); } break; case WM_RBUTTONDOWN: { w32_push_event(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = app_mouse_button_right, }); } break; case WM_RBUTTONUP: { w32_push_event(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_up, .mouse_button = app_mouse_button_right, }); } break; case WM_MBUTTONDOWN: { SetCapture(wnd); w32_push_event(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_down, .mouse_button = app_mouse_button_middle, }); } break; case WM_MBUTTONUP: { ReleaseCapture(); w32_push_event(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_up, .mouse_button = app_mouse_button_middle, }); } break; // case WM_MOUSEMOVE: { // w32_push_event(w32_frame, (app_event_t){ // .kind = app_event_kind_mouse_move, // }); // } break; case WM_MOUSEWHEEL: { int zDelta = GET_WHEEL_DELTA_WPARAM(wparam); w32_push_event(w32_frame, (app_event_t){ .kind = app_event_kind_mouse_wheel, .mouse_wheel_delta = (v3f32_t){0, (f32)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; } b32 w32_get_events(ma_arena_t *arena, app_frame_t *frame, b32 wait) { w32_event_arena = arena; w32_frame = frame; b32 waited = false; MSG msg; if (wait) { if (PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE) == 0) { waited = true; } GetMessageW(&msg, NULL, 0, 0); TranslateMessage(&msg); DispatchMessageW(&msg); } while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return waited; } /////////////////////////////// // canvas functions typedef struct w32_canvas_t w32_canvas_t; struct w32_canvas_t { u32 *memory; HWND window_handle; HBITMAP dib; HDC dib_dc; v2f32_t window_size; }; w32_canvas_t w32_create_canvas(HWND window_handle) { v2f32_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) { v2f32_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); } if (!gladLoadGLLoader((GLADloadproc)w32_load_opengl_fn)) { fatalf("couldn't load opengl!"); } if (wglSwapIntervalEXT) { wglSwapIntervalEXT(1); // vsync } f64 refresh_rate = 60; DEVMODEW devmodew = {0}; if (EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew)) { refresh_rate = (f64)devmodew.dmDisplayFrequency; } app_init(); f64 time_frame_start = w32_seconds_now(); f64 time_delta = 1.0 / refresh_rate; f64 time_update = 0.0; b32 wait_for_events = false; u64 consecutive_missed_frames = 0; u64 missed_frames = 0; u64 frame_counter = 0; for (;;) { app_frame_t frame = {0}; frame.window_size = w32_get_window_size(w32_window_handle); frame.dpr = w32_get_dpr(w32_window_handle); frame.mouse_pos = w32_get_mouse_pos(w32_window_handle); frame.delta = time_delta; frame.update = time_update; frame.frame = frame_counter; b32 waited = w32_get_events(tcx.temp, &frame, wait_for_events); if (waited) { frame.delta = 0.00001; } for (app_event_t *ev = frame.first_event; ev; ev = ev->next) { if (ev->kind == app_event_kind_key_down && ev->key == app_key_escape) { ExitProcess(0); } } if (frame.event_count == 0) { w32_push_event(&frame, (app_event_t){.kind = app_event_kind_update}); } b32 animating = app_update(&frame); wait_for_events = !animating; SwapBuffers(w32_dc); 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 scheduling // 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_counter += 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_frame_start = w32_seconds_now(); } return 0; }