Files
wasm_transcript_browser/src/app/app_win32.c
2025-01-05 10:23:41 +01:00

416 lines
13 KiB
C

#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;
}