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

@@ -255,6 +255,7 @@ type_t type__app_event_kind_t = { type_kind_enum, s8_const_lit("app_event_kind_t
};
type_t type__app_event_t = { type_kind_struct, s8_const_lit("app_event_t"), sizeof(app_event_t),
.members = (type_member_t[]){
{.name = s8_const_lit("next"), .type = &(type_t){type_kind_pointer, s8_const_lit("app_event_t*"), sizeof(void *), .base = &type__app_event_t}, .offset = offsetof(app_event_t, next)},
{.name = s8_const_lit("kind"), .type = &type__app_event_kind_t, .offset = offsetof(app_event_t, kind)},
{.name = s8_const_lit("mouse_button"), .type = &type__app_mouse_button_t, .offset = offsetof(app_event_t, mouse_button)},
{.name = s8_const_lit("key"), .type = &type__app_key_t, .offset = offsetof(app_event_t, key)},
@@ -268,5 +269,13 @@ type_t type__app_event_t = { type_kind_struct, s8_const_lit("app_event_t"), size
{.name = s8_const_lit("window_size"), .type = &type__v2f64_t, .offset = offsetof(app_event_t, window_size)},
{.name = s8_const_lit("mouse_pos"), .type = &type__v2f64_t, .offset = offsetof(app_event_t, mouse_pos)},
},
.count = 12,
.count = 13,
};
type_t type__app_event_list_t = { type_kind_struct, s8_const_lit("app_event_list_t"), sizeof(app_event_list_t),
.members = (type_member_t[]){
{.name = s8_const_lit("first"), .type = &(type_t){type_kind_pointer, s8_const_lit("app_event_t*"), sizeof(void *), .base = &type__app_event_t}, .offset = offsetof(app_event_list_t, first)},
{.name = s8_const_lit("last"), .type = &(type_t){type_kind_pointer, s8_const_lit("app_event_t*"), sizeof(void *), .base = &type__app_event_t}, .offset = offsetof(app_event_list_t, last)},
{.name = s8_const_lit("len"), .type = &type__i32, .offset = offsetof(app_event_list_t, len)},
},
.count = 3,
};

View File

@@ -93,6 +93,7 @@ typedef enum {
typedef struct app_event_t app_event_t;
struct app_event_t {
app_event_t* next;
app_event_kind_t kind;
app_mouse_button_t mouse_button;
app_key_t key;
@@ -105,4 +106,11 @@ struct app_event_t {
f64 dpr;
v2f64_t window_size;
v2f64_t mouse_pos;
};
typedef struct app_event_list_t app_event_list_t;
struct app_event_list_t {
app_event_t* first;
app_event_t* last;
i32 len;
};

View File

@@ -159,12 +159,15 @@ void meta_app(ma_arena_t *arena) {
} app_event_kind_t;
struct app_event_t {
app_event_t *next;
app_event_kind_t kind;
// data present only during events
app_mouse_button_t mouse_button; // @mouse_down @mouse_up
app_key_t key; // @key_down @key_up
s8_t text; // @text
// @todo: test why xyz in js????????????
v3f64_t mouse_wheel_delta; // @mouse_wheel
// always present data
@@ -177,6 +180,12 @@ void meta_app(ma_arena_t *arena) {
v2f64_t window_size;
v2f64_t mouse_pos;
};
struct app_event_list_t {
app_event_t *first;
app_event_t *last;
i32 len;
};
));
sb8_serial_ast_to_code(h, decls);

View File

@@ -1,20 +1,25 @@
/*
.doesn't miss events (always processes all key strokes and buttons etc.)
.replayable / deterministic (that is you can serialize all the events, replay them back and get the same results)
..rendering and update decoupled
.sleeps properly when nothing is happening
.animations probably then should be in the rendering part
.replayable (that is you can serialize all the events, replay them back and get the same results)
..rendering and update decoupled
..animations probably then should be in the rendering part
void app_update(app_event_t *events, i32 event_count) {
loop (events) update(it);
update_result_t *state = NULL; // contains event
loop (events) {
state = update(it);
}
{
app_event_t *ev = events + event_count - 1;
app_event_t *ev = state->ev;
f64 delta_time = app_anim_get_delta_time();
f64 time = app_anim_get_time();
animate(ev, delta_time, time);
render(ev);
animate(state, delta_time, time);
render(state);
}
}
@@ -37,10 +42,10 @@ glb_wasm_export i32 wasm_temp_buff1_len = 127;
glb_wasm_export char wasm_temp_buff2[128] = {[127] = 0x13};
glb_wasm_export i32 wasm_temp_buff2_len = 127;
global f64 wasm_dpr;
global ma_arena_t *wasm_input_text_arena;
global STACK(app_event_t, 64) wasm_events;
global b32 wasm_event_failed_to_queue;
// @todo: event list
global f64 wasm_dpr;
global STACK(app_event_t, 256) wasm_events;
global b32 wasm_event_failed_to_queue;
global f64 wasm_delta_time;
global f64 wasm_time;
@@ -53,7 +58,7 @@ struct wasm_cached_t {
b8 ctrl, alt, meta, shift;
} wasm_cached;
fn void app_update(app_event_t *events, i32 event_count);
fn void app_update(void);
fn void app_init(void);
fn void wasm_add_event(app_event_t event) {
@@ -147,7 +152,7 @@ fn_wasm_export void wasm_key_down(char *key, b32 ctrl, b32 shift, b32 alt, b32 m
return;
}
s8_t text = s8_copy(wasm_input_text_arena, key8);
s8_t text = s8_copy(tcx.temp, key8);
wasm_add_event((app_event_t){
.kind = app_event_kind_text,
.mouse_pos = wasm_cached.mouse_pos,
@@ -180,7 +185,7 @@ fn_wasm_export void wasm_key_up(char *key, b32 ctrl, b32 shift, b32 alt, b32 met
}
fn_wasm_export void wasm_update(f64 width, f64 height, f64 dpr) {
wasm_time = os_get_milliseconds();
wasm_time = os_milliseconds_now();
wasm_delta_time = wasm_time - wasm_last_time_milliseconds;
v2f64_t window_size = (v2f64_t){width / dpr, height / dpr};
@@ -202,18 +207,32 @@ fn_wasm_export void wasm_update(f64 width, f64 height, f64 dpr) {
wasm_events.data[i].window_size = window_size;
}
app_update(wasm_events.data, wasm_events.len);
app_update();
wasm_events.len = 0;
wasm_last_time_milliseconds = wasm_time;
ma_set0(wasm_input_text_arena);
}
fn_wasm_export void wasm_init(void) {
core_init();
wasm_input_text_arena = ma_push_arena(&tcx.perm, kib(1));
app_init();
wasm_app_init_time_milliseconds = os_get_milliseconds();
wasm_app_init_time_milliseconds = os_milliseconds_now();
}
fn app_event_t *app_iterate_events(void) {
return wasm_events.data;
}
fn b32 app_is_event_valid(app_event_t *event) {
return event < (wasm_events.data + wasm_events.len);
}
fn void app_next_event(app_event_t **event) {
event[0] += 1;
}
fn app_event_t *app_get_last_event(void) {
return wasm_events.data + wasm_events.len - 1;
}
fn f64 app_get_anim_time(void) {

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;

270
src/app/app_win32_opengl.c Normal file
View File

@@ -0,0 +1,270 @@
// first load the opengl loading functions
fn b32 w32_load_wgl_fns(void);
// then create opengl context for window
fn b32 w32_create_opengl_context(HDC window_handle_dc, i32 opengl_major, i32 opengl_minor);
// then you can use this to load opengl functions -
// either pass this to glad or load them manually
fn void *w32_load_opengl_fn(const char *proc);
// compile time options
#define W32_OPENGL_DEBUG 1
#define W32_ENABLE_MULTISAMPLING 1
// Symbols taken from GLFW
//
// Executables (but not DLLs) exporting this symbol with this value will be
// automatically directed to the high-performance GPU on Nvidia Optimus systems
// with up-to-date drivers
//
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
// Executables (but not DLLs) exporting this symbol with this value will be
// automatically directed to the high-performance GPU on AMD PowerXpress systems
// with up-to-date drivers
//
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
typedef HGLRC MU_wglCreateContext(HDC unnamedParam1);
typedef BOOL MU_wglMakeCurrent(HDC unnamedParam1, HGLRC unnamedParam2);
typedef BOOL MU_wglDeleteContext(HGLRC unnamedParam1);
typedef void *MU_glGetProcAddress(const char *);
typedef const char *MU_wglGetExtensionsStringARB(HDC hdc);
typedef BOOL MU_wglChoosePixelFormatARB(HDC hdc, const int *piAttribIList, const float *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);
typedef HGLRC MU_wglCreateContextAttribsARB(HDC hDC, HGLRC hshareContext, const int *attribList);
typedef BOOL MU_wglSwapIntervalEXT(int interval);
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_PIXEL_TYPE_ARB 0x2013
#define WGL_TYPE_RGBA_ARB 0x202B
#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define WGL_CONTEXT_FLAGS_ARB 0x2094
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001
#define WGL_SAMPLE_BUFFERS_ARB 0x2041
#define WGL_SAMPLES_ARB 0x2042
global MU_wglChoosePixelFormatARB *wglChoosePixelFormatARB;
global MU_wglCreateContextAttribsARB *wglCreateContextAttribsARB;
global MU_wglSwapIntervalEXT *wglSwapIntervalEXT;
global MU_glGetProcAddress *wgl_get_proc_address;
global void *(*gl_get_proc_address)(const char *str);
global HMODULE opengl_hmodule;
global HGLRC(*mu_wglCreateContext)(HDC unnamedParam1);
global BOOL(*mu_wglMakeCurrent)(HDC unnamedParam1, HGLRC unnamedParam2);
global BOOL(*mu_wglDeleteContext)(HGLRC unnamedParam1);
// compares src string with dstlen characters from dst, returns 1 if they are equal, 0 if not
fn int w32_are_strings_equal(const char *src, const char *dst, size_t dstlen) {
while (*src && dstlen-- && *dst) {
if (*src++ != *dst++) {
return 0;
}
}
return (dstlen && *src == *dst) || (!dstlen && *src == 0);
}
fn void *w32_load_opengl_fn(const char *proc) {
void *func = wgl_get_proc_address(proc);
if (!func) {
func = GetProcAddress(opengl_hmodule, proc);
}
return func;
}
fn b32 w32_load_wgl_fns(void) {
HMODULE opengl32 = LoadLibraryA("opengl32");
assert(opengl32);
if (opengl32) {
opengl_hmodule = opengl32;
wgl_get_proc_address = (MU_glGetProcAddress *)GetProcAddress(opengl32, "wglGetProcAddress");
gl_get_proc_address = w32_load_opengl_fn;
mu_wglCreateContext = (MU_wglCreateContext *)GetProcAddress(opengl32, "wglCreateContext");
mu_wglMakeCurrent = (MU_wglMakeCurrent *)GetProcAddress(opengl32, "wglMakeCurrent");
mu_wglDeleteContext = (MU_wglDeleteContext *)GetProcAddress(opengl32, "wglDeleteContext");
}
if (opengl32 == NULL || mu_wglCreateContext == NULL || gl_get_proc_address == NULL || mu_wglMakeCurrent == NULL || mu_wglDeleteContext == NULL) {
assert(!"Failed to load Opengl wgl functions from opengl32.lib");
}
// to get WGL functions we need valid GL context, so create dummy window for dummy GL contetx
HWND dummy = CreateWindowExW(
0, L"STATIC", L"DummyWindow", WS_OVERLAPPED,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, NULL, NULL);
assert(dummy && "Failed to create dummy window");
HDC dc = GetDC(dummy);
assert(dc && "Failed to get device context for dummy window");
PIXELFORMATDESCRIPTOR desc = {0};
{
desc.nSize = sizeof(desc);
desc.nVersion = 1;
desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
desc.iPixelType = PFD_TYPE_RGBA;
desc.cColorBits = 24;
};
int format = ChoosePixelFormat(dc, &desc);
if (!format) {
assert(!"Cannot choose OpenGL pixel format for dummy window!");
}
int ok = DescribePixelFormat(dc, format, sizeof(desc), &desc);
assert(ok && "Failed to describe OpenGL pixel format");
// reason to create dummy window is that SetPixelFormat can be called only once for the window
if (!SetPixelFormat(dc, format, &desc)) {
assert(!"Cannot set OpenGL pixel format for dummy window!");
}
HGLRC rc = mu_wglCreateContext(dc);
assert(rc && "Failed to create OpenGL context for dummy window");
ok = mu_wglMakeCurrent(dc, rc);
assert(ok && "Failed to make current OpenGL context for dummy window");
// https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_extensions_string.txt
MU_wglGetExtensionsStringARB *wglGetExtensionsStringARB = (MU_wglGetExtensionsStringARB *)gl_get_proc_address("wglGetExtensionsStringARB");
if (!wglGetExtensionsStringARB) {
assert(!"OpenGL does not support WGL_ARB_extensions_string extension!");
}
const char *ext = wglGetExtensionsStringARB(dc);
assert(ext && "Failed to get OpenGL WGL extension string");
const char *start = ext;
for (;;) {
while (*ext != 0 && *ext != ' ') {
ext++;
}
size_t length = ext - start;
if (w32_are_strings_equal("WGL_ARB_pixel_format", start, length)) {
// https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt
wglChoosePixelFormatARB = (MU_wglChoosePixelFormatARB *)gl_get_proc_address("wglChoosePixelFormatARB");
}
else if (w32_are_strings_equal("WGL_ARB_create_context", start, length)) {
// https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt
wglCreateContextAttribsARB = (MU_wglCreateContextAttribsARB *)gl_get_proc_address("wglCreateContextAttribsARB");
}
else if (w32_are_strings_equal("WGL_EXT_swap_control", start, length)) {
// https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt
wglSwapIntervalEXT = (MU_wglSwapIntervalEXT *)gl_get_proc_address("wglSwapIntervalEXT");
}
if (*ext == 0) {
break;
}
ext++;
start = ext;
}
if (!wglChoosePixelFormatARB || !wglCreateContextAttribsARB || !wglSwapIntervalEXT) {
assert(!"OpenGL does not support required WGL extensions for modern context!");
}
BOOL ok_b = mu_wglMakeCurrent(NULL, NULL);
assert(ok_b);
ok_b = mu_wglDeleteContext(rc);
assert(ok_b);
ok = ReleaseDC(dummy, dc);
assert(ok);
ok_b = DestroyWindow(dummy);
assert(ok_b);
return true;
}
fn b32 w32_create_opengl_context(HDC window_handle_dc, i32 opengl_major, i32 opengl_minor) {
// set pixel format for OpenGL context
int attrib[] =
{
WGL_DRAW_TO_WINDOW_ARB,
true,
WGL_SUPPORT_OPENGL_ARB,
true,
WGL_DOUBLE_BUFFER_ARB,
true,
WGL_PIXEL_TYPE_ARB,
WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB,
32,
WGL_DEPTH_BITS_ARB,
24,
WGL_STENCIL_BITS_ARB,
8,
// uncomment for sRGB framebuffer, from WGL_ARB_framebuffer_sRGB extension
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt
// WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, GL_TRUE,
// uncomment for multisampeld framebuffer, from WGL_ARB_multisample extension
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_multisample.txt
#if W32_ENABLE_MULTISAMPLING
WGL_SAMPLE_BUFFERS_ARB,
1,
WGL_SAMPLES_ARB,
4, // 4x MSAA
#endif
0,
};
int format;
UINT formats;
if (!wglChoosePixelFormatARB(window_handle_dc, attrib, 0, 1, &format, &formats) || formats == 0) {
assert(!"OpenGL does not support required pixel format!");
}
PIXELFORMATDESCRIPTOR desc = {0};
desc.nSize = sizeof(desc);
int ok = DescribePixelFormat(window_handle_dc, format, sizeof(desc), &desc);
assert(ok && "Failed to describe OpenGL pixel format");
if (!SetPixelFormat(window_handle_dc, format, &desc)) {
assert(!"Cannot set OpenGL selected pixel format!");
}
// create modern OpenGL context
{
int attrib[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB,
opengl_major,
WGL_CONTEXT_MINOR_VERSION_ARB,
opengl_minor,
WGL_CONTEXT_PROFILE_MASK_ARB,
WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
#if W32_OPENGL_DEBUG
WGL_CONTEXT_FLAGS_ARB,
WGL_CONTEXT_DEBUG_BIT_ARB,
#endif
0,
};
HGLRC rc = wglCreateContextAttribsARB(window_handle_dc, 0, attrib);
assert(rc && "Cannot create modern OpenGL context! OpenGL version not supported?");
BOOL ok = mu_wglMakeCurrent(window_handle_dc, rc);
assert(ok && "Failed to make current OpenGL context");
}
return true;
}