440 lines
14 KiB
C
440 lines
14 KiB
C
#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;
|
|
} |