win32_app

This commit is contained in:
krzosa
2025-01-03 07:53:13 +01:00
parent 05d49eefe8
commit 6933566a86
17 changed files with 832 additions and 200 deletions

View File

@@ -1,8 +1,11 @@
#include "core/core.h"
#include "core/core_inc.h"
#include "app.gen.h"
#include "core/core.c"
#include "core/core_inc.c"
#include "app.gen.c"
#include "app_win32_opengl.c"
#include <windowsx.h>
#pragma comment(linker, "/subsystem:windows")
#pragma comment(lib, "gdi32.lib")
@@ -10,120 +13,361 @@
#pragma comment(lib, "winmm.lib")
b32 w32_good_scheduling = false;
b32 w32_good_scheduling;
WNDCLASSW w32_wc;
HWND w32_window_handle;
HDC w32_dc;
b32 w32_quit_app;
LRESULT CALLBACK w32_window_proc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) {
app_event_list_t w32_event_list;
ma_arena_t *w32_event_arena;
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 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 LRESULT CALLBACK w32_window_proc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_CLOSE: PostQuitMessage(0); break;
case WM_KEYUP: {
app_key_t key = w32_map_wparam_to_app_key(wparam);
case WM_CLOSE: {
ExitProcess(0);
} break;
case WM_KEYDOWN: {
app_key_t key = w32_map_wparam_to_app_key(wparam);
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;
case WM_CHAR: {
//https://handmade.network/forums/t/2011-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names
// WM_UNICHAR
/*
I looked closer to how you process WM_CHAR in example code and I believe it is very wrong. wParam doesn't contain two ushorts joined in one 32-bit value. It is always one UTF-16 value. That means 16-bits. If Windows wants to send you unicode codepoint with value >0xFFFF, then it sends two UTF-16 WM_CHAR messages. Each with one part of UTF-16 surrogate pair. So your first WM_CHAR needs to remember it, and second one construct full unicode codepoint. For example, this is mentioned here: http://www.catch22.net/tuts/unicode-text-editing
*/
} break
default: return DefWindowProcW(wnd, msg, wparam, lparam);
}
return 0;
}
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
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;
app_event_list_t w32_get_events(ma_arena_t *arena) {
w32_event_arena = arena;
memory_zero(&w32_event_list, sizeof(w32_event_list));
typedef unsigned MU_TimeBeginPeriod(unsigned);
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");
}
MSG msg;
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
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;
}
}
}
return w32_event_list;
}
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;
}
///////////////////////////////
// canvas functions
RECT window_rect = {0};
{
window_rect.left = 0;
window_rect.right = 1280;
window_rect.bottom = 732;
window_rect.top = 12;
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);
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_dc = GetDC(w32_window_handle);
assert(w32_dc);
ShowWindow(w32_window_handle, SW_SHOW);
// @todo: rebuild on resize
// Create a writable backbuffer bitmap
uint32_t *mem = 0;
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)1280;
bminfo.bmiHeader.biHeight = (LONG)720;
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;
}
HBITMAP dib = CreateDIBSection(w32_dc, &bminfo, DIB_RGB_COLORS, (void **)&mem, 0, 0);
HDC dib_dc = CreateCompatibleDC(w32_dc);
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 time_frame_start = os_seconds_now();
f64 time_delta = 0.01666666666666;
f64 time_total = 0.0;
f64 time_update = 0.0;
u64 consecutive_missed_frames = 0;
u64 missed_frames = 0;
u64 frame = 0;
for (;;) {
MSG msg;
if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
continue;
}
app_event_list_t event_list = w32_get_events(tcx.temp);
for (i32 y = 0; y < 720; y++) {
for (i32 x = 0; x < 1280; x++) {
mem[x + y * 1280] = 0xFFFF0000;
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);
}
}
SelectObject(dib_dc, dib);
BitBlt(w32_dc, 0, 0, 1280, 720, dib_dc, 0, 0, SRCCOPY);
Sleep(10);
// 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);
///////////////////////////////
// end of frame timings
ma_set0(tcx.temp);
f64 time_update_partial = os_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 = os_seconds_now() - time_frame_start;
while (time_update < time_delta) {
time_update = os_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 = os_seconds_now();
}
return 0;