1129 lines
36 KiB
C++
1129 lines
36 KiB
C++
#include "base.cpp"
|
|
#include "base_unicode.cpp"
|
|
#include "os_windows_base.cpp"
|
|
|
|
#include <Windows.h>
|
|
#include <shellscalingapi.h>
|
|
#include <GL/Gl.h>
|
|
#include <intrin.h>
|
|
|
|
#pragma comment(linker, "/subsystem:windows")
|
|
#pragma comment(lib, "gdi32.lib")
|
|
#pragma comment(lib, "Shcore.lib")
|
|
#pragma comment(lib, "opengl32.lib")
|
|
|
|
// 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;
|
|
|
|
struct Bitmap {
|
|
union {
|
|
U32 *pixels;
|
|
U64 id;
|
|
};
|
|
union {
|
|
Vec2I size;
|
|
struct {
|
|
S32 x, y;
|
|
};
|
|
};
|
|
Vec2 align;
|
|
};
|
|
|
|
enum EventKind {
|
|
EventKind_None,
|
|
EventKind_KeyDown,
|
|
EventKind_KeyUp,
|
|
EventKind_MouseMove,
|
|
EventKind_MouseWheel,
|
|
EventKind_KeyboardText,
|
|
};
|
|
|
|
enum Key {
|
|
Key_None,
|
|
Key_Up,
|
|
Key_Down,
|
|
Key_Left,
|
|
Key_Right,
|
|
Key_Escape,
|
|
Key_F1,
|
|
Key_F2,
|
|
Key_F3,
|
|
Key_F4,
|
|
Key_F5,
|
|
Key_F6,
|
|
Key_F7,
|
|
Key_F8,
|
|
Key_F9,
|
|
Key_F10,
|
|
Key_F11,
|
|
Key_F12,
|
|
Key_MouseLeft,
|
|
Key_MouseRight,
|
|
Key_MouseMiddle,
|
|
Key_0 = '0',
|
|
Key_1,
|
|
Key_2,
|
|
Key_3,
|
|
Key_4,
|
|
Key_5,
|
|
Key_6,
|
|
Key_7,
|
|
Key_8,
|
|
Key_9 = '9',
|
|
Key_A = 'a',
|
|
Key_B,
|
|
Key_C,
|
|
Key_D,
|
|
Key_E,
|
|
Key_F,
|
|
Key_G,
|
|
Key_H,
|
|
Key_I,
|
|
Key_J,
|
|
Key_K,
|
|
Key_L,
|
|
Key_M,
|
|
Key_N,
|
|
Key_O,
|
|
Key_P,
|
|
Key_Q,
|
|
Key_R,
|
|
Key_S,
|
|
Key_T,
|
|
Key_U,
|
|
Key_V,
|
|
Key_W,
|
|
Key_X,
|
|
Key_Y,
|
|
Key_Z = 'z',
|
|
Key_Count = 256,
|
|
};
|
|
|
|
#define KEY_MAPPING \
|
|
X(Up, VK_UP) \
|
|
X(Down, VK_DOWN) \
|
|
X(Left, VK_LEFT) \
|
|
X(Right, VK_RIGHT) \
|
|
X(Escape, VK_ESCAPE) \
|
|
X(F1, VK_F1) \
|
|
X(F2, VK_F2) \
|
|
X(F3, VK_F3) \
|
|
X(F4, VK_F4) \
|
|
X(F5, VK_F5) \
|
|
X(F6, VK_F6) \
|
|
X(F7, VK_F7) \
|
|
X(F8, VK_F8) \
|
|
X(F9, VK_F9) \
|
|
X(F10, VK_F10) \
|
|
X(F11, VK_F11) \
|
|
X(F12, VK_F12) \
|
|
X(A, 65) \
|
|
X(B, 66) \
|
|
X(C, 67) \
|
|
X(D, 68) \
|
|
X(E, 69) \
|
|
X(F, 70) \
|
|
X(G, 71) \
|
|
X(H, 72) \
|
|
X(I, 73) \
|
|
X(J, 74) \
|
|
X(K, 75) \
|
|
X(L, 76) \
|
|
X(M, 77) \
|
|
X(N, 78) \
|
|
X(O, 79) \
|
|
X(P, 80) \
|
|
X(Q, 81) \
|
|
X(R, 82) \
|
|
X(S, 83) \
|
|
X(T, 84) \
|
|
X(U, 85) \
|
|
X(V, 86) \
|
|
X(W, 87) \
|
|
X(X, 88) \
|
|
X(Y, 89) \
|
|
X(Z, 90) \
|
|
X(0, 48) \
|
|
X(1, 49) \
|
|
X(2, 50) \
|
|
X(3, 51) \
|
|
X(4, 52) \
|
|
X(5, 53) \
|
|
X(6, 54) \
|
|
X(7, 55) \
|
|
X(8, 56) \
|
|
X(9, 57)
|
|
|
|
struct DigitalKey {
|
|
bool pressed;
|
|
bool down;
|
|
bool released;
|
|
};
|
|
|
|
enum RenderBackend {
|
|
RenderBackend_Software,
|
|
RenderBackend_OpenGL1,
|
|
};
|
|
|
|
struct OS {
|
|
bool quit;
|
|
bool initialized;
|
|
Arena *frame_arena;
|
|
Arena *perm_arena;
|
|
|
|
F64 ms_per_frame;
|
|
bool window_resizable;
|
|
bool window_was_resized;
|
|
String window_title;
|
|
Vec2I window_size;
|
|
Vec2I window_pos;
|
|
RenderBackend render_backend;
|
|
|
|
Bitmap *screen;
|
|
Vec2I monitor_size;
|
|
F32 dpi_scale;
|
|
|
|
F64 fps;
|
|
F64 delta_time;
|
|
F64 time;
|
|
U64 frame;
|
|
F64 update_time;
|
|
U64 update_begin_cycles;
|
|
U64 update_end_cycles;
|
|
F64 frame_start_time;
|
|
F64 app_start_time;
|
|
|
|
B32 cursor_visible;
|
|
DigitalKey key[Key_Count];
|
|
F32 mouse_wheel;
|
|
Vec2I mouse_pos;
|
|
Vec2I delta_mouse_pos;
|
|
|
|
String text;
|
|
|
|
char platform[256];
|
|
struct {
|
|
char *vendor;
|
|
char *version;
|
|
char *renderer;
|
|
char *extensions;
|
|
} opengl;
|
|
};
|
|
|
|
struct FontGlyph {
|
|
F32 xadvance;
|
|
U32 codepoint;
|
|
Bitmap bitmap;
|
|
};
|
|
|
|
struct Font {
|
|
F32 line_advance;
|
|
F32 ascent;
|
|
F32 descent;
|
|
F32 height;
|
|
FontGlyph *glyphs;
|
|
U32 glyphs_len;
|
|
};
|
|
|
|
struct Audio;
|
|
#define AUDIO_CALLBACK(name) void name(Audio *audio, U32* buffer, U32 frames_to_fill)
|
|
typedef AUDIO_CALLBACK(AudioCallback);
|
|
|
|
struct Audio {
|
|
AudioCallback *callback;
|
|
B32 initialized;
|
|
U32 samples_per_second;
|
|
U32 number_of_channels;
|
|
|
|
// NOTE: one frame is 2 samples (left, right) 32 bits if
|
|
// one sample is equal 16 bit
|
|
U32 buffer_frame_count;
|
|
U32 latency_frame_count;
|
|
S32 bits_per_sample;
|
|
|
|
char platform[128];
|
|
};
|
|
|
|
OS os;
|
|
#include "base_math.cpp"
|
|
|
|
typedef HRESULT tSetProcessDpiAwareness(PROCESS_DPI_AWARENESS);
|
|
typedef MMRESULT TimeBeginPeriod(MMRESULT);
|
|
constexpr DWORD window_style_simplified = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
|
|
constexpr DWORD window_style_resizable = WS_OVERLAPPEDWINDOW;
|
|
constexpr DWORD window_style_borderless = WS_POPUP;
|
|
|
|
struct Win32Bitmap {
|
|
Bitmap bitmap;
|
|
HDC dc;
|
|
HBITMAP dib;
|
|
};
|
|
|
|
struct Win32FontCtx {
|
|
HFONT font;
|
|
Win32Bitmap bitmap;
|
|
TEXTMETRIC text_metric;
|
|
Font result;
|
|
};
|
|
|
|
struct OS_Win32 {
|
|
HWND window;
|
|
HDC window_dc;
|
|
Win32Bitmap screen;
|
|
HINSTANCE instance;
|
|
int show_cmd;
|
|
char *cmd_line;
|
|
bool good_scheduling;
|
|
void *main_fiber;
|
|
void *msg_fiber;
|
|
U8 text_buff[32];
|
|
Vec2I prev_window_size;
|
|
Vec2I prev_mouse_pos;
|
|
};
|
|
static_assert(sizeof(OS_Win32) < 256, "Too big");
|
|
#define w32(a) (*(OS_Win32 *)(a).platform)
|
|
|
|
api Bitmap
|
|
bitmap(U32 *pixels, Vec2I size, Vec2 align_in_pixels = {}) {
|
|
Bitmap result;
|
|
result.pixels = pixels;
|
|
result.size = size;
|
|
result.align = align_in_pixels;
|
|
return result;
|
|
}
|
|
|
|
function Win32Bitmap
|
|
win32_create_bitmap(Vec2I size) {
|
|
Win32Bitmap result;
|
|
result.bitmap.size = size;
|
|
if (result.bitmap.size.y < 0)
|
|
result.bitmap.size.y = -result.bitmap.size.y;
|
|
|
|
HDC hdc = GetDC(0);
|
|
BITMAPINFO bminfo = {};
|
|
bminfo.bmiHeader.biSize = sizeof(bminfo.bmiHeader);
|
|
bminfo.bmiHeader.biWidth = (LONG)size.x;
|
|
bminfo.bmiHeader.biHeight = (LONG)-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;
|
|
|
|
void *mem = 0;
|
|
result.dib = CreateDIBSection(hdc, &bminfo, DIB_RGB_COLORS, (void **)&mem, 0, 0);
|
|
assert_msg(mem != 0, "Failed to create win32 bitmap");
|
|
result.dc = CreateCompatibleDC(hdc);
|
|
result.bitmap.pixels = (U32 *)mem;
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
win32_destroy_bitmap(Win32Bitmap *b) {
|
|
if (b->bitmap.pixels) {
|
|
b->bitmap.pixels = 0;
|
|
DeleteDC(b->dc);
|
|
DeleteObject(b->dib);
|
|
}
|
|
}
|
|
|
|
function void
|
|
win32_write_user_text(U8 *text, S32 len){
|
|
if(os.text.len+len < buff_cap(w32(os).text_buff)){
|
|
for(S32 i = 0; i < len; i++){
|
|
os.text.str[os.text.len++] = text[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
function LRESULT
|
|
CALLBACK win32_window_proc(HWND window, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
EventKind kind = EventKind_None;
|
|
Key key = Key_None;
|
|
LRESULT result = 0;
|
|
switch (uMsg) {
|
|
case WM_CLOSE:
|
|
DestroyWindow(window);
|
|
os.quit = 1;
|
|
break;
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
os.quit = 1;
|
|
break;
|
|
case WM_SYSKEYDOWN:
|
|
case WM_KEYDOWN: {
|
|
kind = EventKind_KeyDown;
|
|
switch (wParam) {
|
|
|
|
#define X(button, win) \
|
|
case win: \
|
|
key = Key_##button; \
|
|
break;
|
|
|
|
KEY_MAPPING
|
|
}
|
|
} break;
|
|
case WM_SYSKEYUP:
|
|
case WM_KEYUP: {
|
|
kind = EventKind_KeyUp;
|
|
switch (wParam) {
|
|
KEY_MAPPING
|
|
#undef X
|
|
}
|
|
} break;
|
|
case WM_TIMER: {
|
|
SwitchToFiber(w32(os).main_fiber);
|
|
} break;
|
|
case WM_ENTERMENULOOP:
|
|
case WM_ENTERSIZEMOVE: {
|
|
SetTimer(w32(os).window, 0, 1, 0);
|
|
} break;
|
|
case WM_EXITMENULOOP:
|
|
case WM_EXITSIZEMOVE: {
|
|
KillTimer(w32(os).window, 0);
|
|
} break;
|
|
case WM_MOUSEMOVE: {
|
|
POINT p;
|
|
GetCursorPos(&p);
|
|
ScreenToClient(w32(os).window, &p);
|
|
os.mouse_pos = vec2i(p.x, p.y);
|
|
os.mouse_pos.y = os.window_size.y - os.mouse_pos.y;
|
|
kind = EventKind_MouseMove;
|
|
} break;
|
|
case WM_LBUTTONDOWN: {
|
|
kind = EventKind_KeyDown;
|
|
key = Key_MouseLeft;
|
|
} break;
|
|
case WM_LBUTTONUP: {
|
|
kind = EventKind_KeyUp;
|
|
key = Key_MouseLeft;
|
|
} break;
|
|
case WM_RBUTTONDOWN: {
|
|
kind = EventKind_KeyDown;
|
|
key = Key_MouseRight;
|
|
} break;
|
|
case WM_RBUTTONUP: {
|
|
kind = EventKind_KeyUp;
|
|
key = Key_MouseRight;
|
|
} break;
|
|
case WM_MBUTTONDOWN: {
|
|
kind = EventKind_KeyDown;
|
|
key = Key_MouseMiddle;
|
|
} break;
|
|
case WM_MBUTTONUP: {
|
|
kind = EventKind_KeyUp;
|
|
key = Key_MouseMiddle;
|
|
} break;
|
|
case WM_MOUSEWHEEL: {
|
|
if ((int)wParam > 0)
|
|
os.mouse_wheel = 1;
|
|
else
|
|
os.mouse_wheel = -1;
|
|
kind = EventKind_MouseWheel;
|
|
} break;
|
|
case WM_CHAR: {
|
|
kind = EventKind_KeyboardText;
|
|
// @Note(Krzosa): No surrogate extensions
|
|
UTF32_Result decode = utf16_to_utf32((U16 *)&wParam, 1);
|
|
if(!decode.error){
|
|
UTF8_Result encode = utf32_to_utf8(decode.out_str);
|
|
if(!encode.error){
|
|
win32_write_user_text(encode.out_str, encode.len);
|
|
}
|
|
else win32_write_user_text((U8 *)"?", 1);
|
|
}
|
|
else win32_write_user_text((U8 *)"?", 1);
|
|
} break;
|
|
default:
|
|
result = DefWindowProcW(window, uMsg, wParam, lParam);
|
|
break;
|
|
}
|
|
|
|
if (kind) {
|
|
if (kind == EventKind_KeyDown) {
|
|
if (os.key[key].down == 0)
|
|
os.key[key].pressed = 1;
|
|
os.key[key].down = 1;
|
|
} else if (kind == EventKind_KeyUp) {
|
|
os.key[key].released = 1;
|
|
os.key[key].down = 0;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
CALLBACK _os_fiber_event_proc(void *data) {
|
|
unused(data);
|
|
for (;;) {
|
|
MSG msg;
|
|
while (PeekMessageW(&msg, w32(os).window, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
SwitchToFiber(w32(os).main_fiber);
|
|
}
|
|
}
|
|
|
|
function Vec2I
|
|
get_window_size(HWND window) {
|
|
Vec2I result;
|
|
RECT window_rect;
|
|
GetClientRect(window, &window_rect);
|
|
result.x = window_rect.right - window_rect.left;
|
|
result.y = window_rect.bottom - window_rect.top;
|
|
return result;
|
|
}
|
|
|
|
function Vec2I
|
|
get_window_size_with_border(HWND window) {
|
|
RECT ClientRect;
|
|
GetWindowRect(window, &ClientRect);
|
|
Vec2I draw_area;
|
|
draw_area.x = (ClientRect.right - ClientRect.left);
|
|
draw_area.y = (ClientRect.bottom - ClientRect.top);
|
|
return draw_area;
|
|
}
|
|
|
|
function Vec2I
|
|
get_border_size(HWND window) {
|
|
Vec2I client = get_window_size(window);
|
|
Vec2I wind_size = get_window_size_with_border(window);
|
|
Vec2I result = vec2i(wind_size.x - client.x, wind_size.y - client.y);
|
|
return result;
|
|
}
|
|
|
|
api void os_quit() { os.quit = 1; }
|
|
api void os_set_fps(F64 fps) { os.ms_per_frame = 1 / fps; }
|
|
api void os_set_ms_per_frame(F64 ms) { os.ms_per_frame = ms; }
|
|
|
|
api void os_show_cursor(B32 status) {
|
|
ShowCursor(status);
|
|
os.cursor_visible = status;
|
|
}
|
|
|
|
api void os_set_window_title(String title) {
|
|
Scratch scratch;
|
|
BOOL result = SetWindowTextA(w32(os).window, (char *)title.str);
|
|
assert_msg(result != 0, "Failed to set window title");
|
|
os.window_title = title;
|
|
}
|
|
|
|
api void os_set_window_size(S32 x, S32 y) {
|
|
Vec2I border = get_border_size(w32(os).window);
|
|
int actual_width = (int)(x + border.x);
|
|
int actual_height = (int)(y + border.y);
|
|
bool result = SetWindowPos(w32(os).window, 0, 0, 0, actual_width, actual_height, SWP_NOMOVE | SWP_NOOWNERZORDER);
|
|
assert_msg(result, "SetWindowPos returned invalid value");
|
|
os.window_size = get_window_size(w32(os).window);
|
|
}
|
|
|
|
api void os_pull_state() {
|
|
os.window_was_resized = false;
|
|
os.window_size = get_window_size(w32(os).window);
|
|
if (os.window_size != w32(os).prev_window_size) {
|
|
os.window_was_resized = true;
|
|
w32(os).prev_window_size = os.window_size;
|
|
}
|
|
os.monitor_size = vec2i(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
|
|
|
|
// @Note: Client relative
|
|
POINT point = {0, 0};
|
|
ClientToScreen(w32(os).window, &point);
|
|
os.window_pos.x = point.x;
|
|
os.window_pos.y = point.y;
|
|
|
|
// @Note: Get DPI scale
|
|
UINT dpi = GetDpiForWindow(w32(os).window);
|
|
assert_msg(dpi != 0, "Failed to get dpi for window");
|
|
os.dpi_scale = (F32)dpi / 96.f;
|
|
|
|
// @Note: Reset text
|
|
os.text.len = 0;
|
|
// @Note: Reset keys
|
|
for (int i = 0; i < Key_Count; i++) {
|
|
os.key[i].released = 0;
|
|
os.key[i].pressed = 0;
|
|
}
|
|
os.mouse_wheel = 0;
|
|
SwitchToFiber(w32(os).msg_fiber);
|
|
if (!os.quit) {
|
|
os.delta_mouse_pos = w32(os).prev_mouse_pos - os.mouse_pos;
|
|
w32(os).prev_mouse_pos = os.mouse_pos;
|
|
|
|
// @Note: Resize
|
|
if (os.render_backend == RenderBackend_Software) {
|
|
if (os.window_size != w32(os).screen.bitmap.size && os.window_size.x != 0 && os.window_size.y != 0) {
|
|
win32_destroy_bitmap(&w32(os).screen);
|
|
w32(os).screen = win32_create_bitmap(vec2i(os.window_size.x, -(os.window_size.y)));
|
|
os.screen = &w32(os).screen.bitmap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
api void os_init_software_render() { os.render_backend = RenderBackend_Software; }
|
|
|
|
api B32 os_init_opengl() {
|
|
PIXELFORMATDESCRIPTOR p = {};
|
|
p.nSize = sizeof(p);
|
|
p.nVersion = 1;
|
|
p.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
|
p.iPixelType = PFD_TYPE_RGBA;
|
|
p.cColorBits = 32;
|
|
p.cDepthBits = 24;
|
|
p.cStencilBits = 8;
|
|
p.iLayerType = PFD_MAIN_PLANE;
|
|
|
|
S32 pixel_format = ChoosePixelFormat(w32(os).window_dc, &p);
|
|
if (pixel_format != 0) {
|
|
if (SetPixelFormat(w32(os).window_dc, pixel_format, &p)) {
|
|
HGLRC gl_ctx = wglCreateContext(w32(os).window_dc);
|
|
if (gl_ctx != NULL) {
|
|
if (wglMakeCurrent(w32(os).window_dc, gl_ctx)) {
|
|
// Success
|
|
}
|
|
else {
|
|
log_error("Failed on wglMakeCurrent!");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_error("Failed on wglCreateContext!");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_error("Failed on SetPixelFormat!");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_error("Failed on ChoosePixelFormat!");
|
|
return false;
|
|
}
|
|
|
|
os.opengl.vendor = (char *)glGetString(GL_VENDOR);
|
|
os.opengl.renderer = (char *)glGetString(GL_RENDERER);
|
|
os.opengl.version = (char *)glGetString(GL_VERSION);
|
|
os.opengl.extensions = (char *)glGetString(GL_EXTENSIONS);
|
|
os.render_backend = RenderBackend_OpenGL1;
|
|
|
|
return true;
|
|
}
|
|
|
|
api B32 os_init() {
|
|
HMODULE shcore = LoadLibraryA("Shcore.dll");
|
|
if (shcore) {
|
|
tSetProcessDpiAwareness *set_dpi_awr =
|
|
(tSetProcessDpiAwareness *)GetProcAddress(shcore, "SetProcessDpiAwareness");
|
|
if (set_dpi_awr) {
|
|
HRESULT hr = set_dpi_awr(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
assert_msg(SUCCEEDED(hr), "Failed to set dpi awareness");
|
|
}
|
|
}
|
|
|
|
HMODULE winmm = LoadLibraryA("winmm.dll");
|
|
if (winmm) {
|
|
TimeBeginPeriod *timeBeginPeriod = (TimeBeginPeriod *)GetProcAddress(winmm, "timeBeginPeriod");
|
|
if (timeBeginPeriod) {
|
|
if (timeBeginPeriod(1) == TIMERR_NOERROR) {
|
|
w32(os).good_scheduling = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD window_style_chosen = window_style_resizable;
|
|
if (!os.window_resizable)
|
|
window_style_chosen = window_style_simplified;
|
|
|
|
os.app_start_time = os_time();
|
|
os.frame_start_time = os.app_start_time;
|
|
os.update_begin_cycles = __rdtsc();
|
|
|
|
Scratch scratch;
|
|
WNDCLASSW wc = {};
|
|
wc.lpfnWndProc = win32_window_proc;
|
|
wc.hInstance = w32(os).instance;
|
|
wc.lpszClassName = (LPCWSTR)string8_to_string16(scratch, os.window_title).str;
|
|
wc.hCursor = LoadCursor(0, IDC_ARROW);
|
|
wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
|
|
if (!RegisterClassW(&wc)) {
|
|
log_error("Failed to create window class!");
|
|
return false;
|
|
}
|
|
|
|
RECT window_rect;
|
|
window_rect.left = (LONG)os.window_pos.x;
|
|
window_rect.top = (LONG)os.window_pos.y;
|
|
window_rect.right = (LONG)os.window_size.x + window_rect.left;
|
|
window_rect.bottom = (LONG)os.window_size.y + window_rect.top;
|
|
AdjustWindowRectEx(&window_rect, window_style_chosen, false, 0);
|
|
|
|
w32(os).window = CreateWindowW(wc.lpszClassName, wc.lpszClassName, window_style_chosen, window_rect.left, window_rect.top, window_rect.right - window_rect.left,window_rect.bottom - window_rect.top, NULL, NULL, w32(os).instance, NULL);
|
|
if (w32(os).window == 0) {
|
|
log_error("Failed to create window!");
|
|
return false;
|
|
}
|
|
|
|
ShowWindow(w32(os).window, w32(os).show_cmd);
|
|
UpdateWindow(w32(os).window);
|
|
w32(os).window_dc = GetDC(w32(os).window);
|
|
|
|
w32(os).main_fiber = ConvertThreadToFiber(0);
|
|
assert_msg(w32(os).main_fiber, "Failed to create main fiber");
|
|
w32(os).msg_fiber = CreateFiber(0, _os_fiber_event_proc, 0);
|
|
assert_msg(w32(os).msg_fiber, "Failed to create message fiber");
|
|
|
|
if (os.cursor_visible == false)
|
|
os_show_cursor(false);
|
|
|
|
switch (os.render_backend) {
|
|
case RenderBackend_Software: {
|
|
os_init_software_render();
|
|
} break;
|
|
case RenderBackend_OpenGL1: {
|
|
os_init_opengl();
|
|
} break;
|
|
default: assert_msg(0, "Invalid value for render backend");
|
|
break;
|
|
}
|
|
|
|
os_pull_state();
|
|
os.initialized = true;
|
|
return true;
|
|
}
|
|
|
|
api bool os_game_loop() {
|
|
assert_msg(os.initialized, "Platform is not initialized! Please call os_init");
|
|
switch (os.render_backend) {
|
|
case RenderBackend_Software: {
|
|
if (os.screen) { // @Note: Draw screen
|
|
U32 *p = os.screen->pixels;
|
|
for (int y = 0; y < os.screen->y; y++) {
|
|
for (int x = 0; x < os.screen->x; x++) {
|
|
*p = ((*p & 0xff000000)) | ((*p & 0x00ff0000) >> 16) | ((*p & 0x0000ff00)) |
|
|
((*p & 0x000000ff) << 16);
|
|
p += 1;
|
|
}
|
|
}
|
|
|
|
HDC hdc = w32(os).window_dc;
|
|
SelectObject(w32(os).screen.dc, w32(os).screen.dib);
|
|
BitBlt(hdc, 0, 0, (LONG)os.screen->size.x, (LONG)os.screen->size.y, w32(os).screen.dc, 0, 0, SRCCOPY);
|
|
}
|
|
} break;
|
|
case RenderBackend_OpenGL1: {
|
|
SwapBuffers(w32(os).window_dc);
|
|
} break;
|
|
default:
|
|
assert_msg(0, "Please select a rendering backend!");
|
|
break;
|
|
}
|
|
arena_clear(os.frame_arena);
|
|
os.update_time = os_time() - os.frame_start_time;
|
|
os.update_end_cycles = __rdtsc();
|
|
F64 frame_time = os.update_time;
|
|
F64 ms_per_frame = os.ms_per_frame;
|
|
if (frame_time < ms_per_frame) {
|
|
if (w32(os).good_scheduling) {
|
|
// @Todo: I have no idea if letting over sleep is bad or not
|
|
// Busy waiting is chugging cpu alot more, not sure what to do
|
|
F64 time_to_sleep = (ms_per_frame - frame_time) * 1000;
|
|
if (time_to_sleep > 0) {
|
|
Sleep((DWORD)time_to_sleep);
|
|
}
|
|
}
|
|
|
|
do {
|
|
frame_time = os_time() - os.frame_start_time;
|
|
} while (frame_time < ms_per_frame);
|
|
}
|
|
os.frame++;
|
|
os.delta_time = frame_time;
|
|
os.fps = 1 / os.delta_time;
|
|
os.time += os.delta_time;
|
|
os.frame_start_time = os_time();
|
|
os.update_begin_cycles = __rdtsc();
|
|
os_pull_state();
|
|
return !os.quit;
|
|
}
|
|
|
|
int main(int argc, char **argv);
|
|
int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmd_line, int show_cmd) {
|
|
thread_ctx_init();
|
|
w32(os).instance = instance;
|
|
w32(os).show_cmd = show_cmd;
|
|
w32(os).cmd_line = cmd_line;
|
|
|
|
Arena frame_arena = {};
|
|
arena_init(&frame_arena, "Frame arena"_s);
|
|
os.perm_arena = &pernament_arena;
|
|
os.frame_arena = &frame_arena;
|
|
|
|
os.dpi_scale = 1;
|
|
os.text.str = w32(os).text_buff;
|
|
os.window_title = "Have a good day!"_s;
|
|
os.window_pos.x = 0;
|
|
os.window_pos.y = 50;
|
|
os.window_size.x = 1280;
|
|
os.window_size.y = 720;
|
|
os.ms_per_frame = 1.f / 60.f;
|
|
os.cursor_visible = true;
|
|
|
|
if(AttachConsole(-1)) {
|
|
freopen("CONIN$", "r",stdin);
|
|
freopen("CONOUT$", "w",stdout);
|
|
freopen("CONOUT$", "w",stderr);
|
|
}
|
|
|
|
return main(__argc, __argv);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Font API
|
|
//-----------------------------------------------------------------------------
|
|
function FontGlyph extract_glyph(Allocator *arena, Win32FontCtx *ctx, wchar_t glyph) {
|
|
SIZE size;
|
|
GetTextExtentPoint32W(ctx->bitmap.dc, &glyph, 1, &size);
|
|
TextOutW(ctx->bitmap.dc, 0, 0, &glyph, 1);
|
|
|
|
// @Note: Find bitmap edges
|
|
int minx = 100000;
|
|
int miny = 100000;
|
|
int maxx = -100000;
|
|
int maxy = -100000;
|
|
for (int y = 499; y >= 500 - size.cy; y--) {
|
|
for (int x = 0; x < size.cx; x++) {
|
|
if (ctx->bitmap.bitmap.pixels[x + y * (int)ctx->bitmap.bitmap.size.x] != 0) {
|
|
if (minx > x)
|
|
minx = x;
|
|
if (miny > y)
|
|
miny = y;
|
|
if (maxx < x)
|
|
maxx = x;
|
|
if (maxy < y)
|
|
maxy = y;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(minx >= 0 && miny >= 0);
|
|
int bwidth = maxx - minx + 1;
|
|
int bheight = maxy - miny + 1;
|
|
U32 *cropped =
|
|
(U32 *)exp_alloc(arena, sizeof(U32) * (U32)(bwidth) * (U32)(bheight));
|
|
for (int y = miny; y <= maxy; y++) {
|
|
for (int x = minx; x <= maxx; x++) {
|
|
U32 value = ctx->bitmap.bitmap.pixels[x + y * (int)ctx->bitmap.bitmap.size.x];
|
|
U32 *dst = cropped + ((size_t)(x - minx) + (size_t)(y - miny) * bwidth);
|
|
#if 1 // Premultiplied alpha
|
|
F32 alpha = (F32)((value & 0x000000ff) >> 0);
|
|
F32 rgb = (F32)0xff*(alpha/255.f);
|
|
U8 val = (U8)(rgb + 0.5f);
|
|
*dst = (((U32)(alpha+0.5f) << 24) | val << 16 | val << 8 | val);
|
|
#else
|
|
U8 grey = (value & 0x000000ff);
|
|
*dst = (grey << 24 | 0xff << 16 | 0xff << 8 | 0xff);
|
|
#endif
|
|
}
|
|
}
|
|
exp_alloc(arena, sizeof(U32) * (U32)bwidth);
|
|
|
|
// @Note: Calculate char metrics
|
|
int glyph_descent = (499 - size.cy) - miny;
|
|
|
|
FontGlyph result;
|
|
INT width;
|
|
GetCharWidth32W(ctx->bitmap.dc, glyph, glyph, &width);
|
|
result.xadvance = (F32)width;
|
|
result.bitmap = bitmap(cropped, vec2i(bwidth, bheight),
|
|
vec2((F32)-minx, (F32)ctx->text_metric.tmDescent + (F32)glyph_descent));
|
|
return result;
|
|
}
|
|
|
|
function Win32FontCtx begin_font_extraction(char *filename, char *font_name, S64 height) {
|
|
assert_msg(height < 500, "Height of font over 500");
|
|
Win32FontCtx ctx = {};
|
|
if (filename) {
|
|
int fonts_added = AddFontResourceExA(filename, FR_PRIVATE, 0);
|
|
assert_msg(fonts_added != 0, "AddFontResourceEx added 0 fonts");
|
|
}
|
|
|
|
ctx.bitmap = win32_create_bitmap(vec2i(500, -500));
|
|
ctx.font =
|
|
CreateFontA((S32)height, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE, font_name);
|
|
assert_msg(ctx.font != NULL, "CreateFont returned a 0 pointer");
|
|
SelectObject(ctx.bitmap.dc, ctx.bitmap.dib);
|
|
SelectObject(ctx.bitmap.dc, ctx.font);
|
|
GetTextMetrics(ctx.bitmap.dc, &ctx.text_metric);
|
|
ctx.result.height = (F32)height;
|
|
ctx.result.descent = (F32)ctx.text_metric.tmDescent;
|
|
ctx.result.ascent = (F32)ctx.text_metric.tmAscent;
|
|
ctx.result.line_advance = (F32)(height + ctx.text_metric.tmExternalLeading);
|
|
SetBkColor(ctx.bitmap.dc, RGB(0, 0, 0));
|
|
SetTextColor(ctx.bitmap.dc, RGB(255, 255, 255));
|
|
return ctx;
|
|
}
|
|
|
|
function Font end_font_extraction(Win32FontCtx *ctx) {
|
|
win32_destroy_bitmap(&ctx->bitmap);
|
|
DeleteObject(ctx->font);
|
|
return ctx->result;
|
|
}
|
|
|
|
api Font os_load_font(Allocator *arena, S32 height, const char *font_name, const char *filename) {
|
|
FontGlyph *glyphs = exp_alloc_array(arena, FontGlyph, 96, AF_ZeroMemory);
|
|
Win32FontCtx font_ctx = begin_font_extraction((char *)filename, (char *)font_name, height);
|
|
for (U32 i = '!'; i <= '~'; i++) {
|
|
glyphs[i - '!'] = extract_glyph(arena, &font_ctx, (wchar_t)i);
|
|
}
|
|
Font font = end_font_extraction(&font_ctx);
|
|
font.glyphs = glyphs;
|
|
font.glyphs_len = '~' - '!';
|
|
return font;
|
|
}
|
|
|
|
#undef w32
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Audio
|
|
//-----------------------------------------------------------------------------
|
|
#include <objbase.h>
|
|
#include <audioclient.h>
|
|
#include <audiopolicy.h>
|
|
#include <mmdeviceapi.h>
|
|
|
|
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
|
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
|
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
|
|
|
|
// NOTE: typedefines for the functions which are goint to be loaded
|
|
typedef HRESULT CoCreateInstanceFunction(REFCLSID rclsid, LPUNKNOWN *pUnkOuter, DWORD dwClsContext,
|
|
REFIID riid, LPVOID *ppv);
|
|
typedef HRESULT CoInitializeExFunction(LPVOID pvReserved, DWORD dwCoInit);
|
|
|
|
// NOTE: empty functions(stubs) which are used when library fails to load
|
|
HRESULT CoCreateInstanceStub(REFCLSID rclsid, LPUNKNOWN *pUnkOuter, DWORD dwClsContext, REFIID riid,
|
|
LPVOID *ppv) {
|
|
unused(rclsid);
|
|
unused(pUnkOuter);
|
|
unused(dwClsContext);
|
|
unused(riid);
|
|
unused(ppv);
|
|
return S_FALSE;
|
|
}
|
|
|
|
HRESULT CoInitializeExStub(LPVOID pvReserved, DWORD dwCoInit) {
|
|
unused(pvReserved);
|
|
unused(dwCoInit);
|
|
return S_FALSE;
|
|
}
|
|
|
|
// NOTE: pointers to the functions from the dll
|
|
CoCreateInstanceFunction *CoCreateInstanceFunctionPointer = CoCreateInstanceStub;
|
|
CoInitializeExFunction *CoInitializeExFunctionPointer = CoInitializeExStub;
|
|
|
|
// NOTE: Number of REFERENCE_TIME units per second
|
|
// One unit is equal to 100 nano seconds
|
|
#define REF_TIMES_PER_SECOND 10000000
|
|
#define REF_TIMES_PER_MSECOND 10000
|
|
#define w32(a) (*(Audio_Win32 *)(a)->platform)
|
|
|
|
struct Audio_Win32 {
|
|
IMMDevice *device;
|
|
IAudioClient *audio_client;
|
|
|
|
IMMDeviceEnumerator *device_enum;
|
|
IAudioRenderClient *audio_render_client;
|
|
IAudioCaptureClient *audio_capture_client;
|
|
};
|
|
static_assert(sizeof(Audio::platform) > sizeof(Audio_Win32),
|
|
"Audio::platform is too small to hold Audio_Win32 struct");
|
|
|
|
// Load COM Library functions dynamically,
|
|
// this way sound is not necessary to run the game
|
|
function B32 win32_load_com() {
|
|
B32 result = true;
|
|
HMODULE ole32Library = LoadLibraryA("ole32.dll");
|
|
if (ole32Library) {
|
|
CoCreateInstanceFunctionPointer =
|
|
(CoCreateInstanceFunction *)GetProcAddress(ole32Library, "CoCreateInstance");
|
|
if (!CoCreateInstanceFunctionPointer) {
|
|
CoCreateInstanceFunctionPointer = CoCreateInstanceStub;
|
|
log_error("CoCreateInstance failed to load");
|
|
result = false;
|
|
}
|
|
CoInitializeExFunctionPointer = (CoInitializeExFunction *)GetProcAddress(ole32Library, "CoInitializeEx");
|
|
if (!CoInitializeExFunctionPointer) {
|
|
CoInitializeExFunctionPointer = CoInitializeExStub;
|
|
log_error("CoInitializeEx failed to load");
|
|
result = false;
|
|
}
|
|
} else {
|
|
CoCreateInstanceFunctionPointer = CoCreateInstanceStub;
|
|
CoInitializeExFunctionPointer = CoInitializeExStub;
|
|
log_error("Failed to load OLE32.dll");
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
api void os_clean_audio(Audio *audio) {
|
|
if (w32(audio).audio_client)
|
|
w32(audio).audio_client->Stop();
|
|
if (w32(audio).device_enum)
|
|
w32(audio).device_enum->Release();
|
|
if (w32(audio).device)
|
|
w32(audio).device->Release();
|
|
if (w32(audio).audio_client)
|
|
w32(audio).audio_client->Release();
|
|
if (w32(audio).audio_render_client)
|
|
w32(audio).audio_render_client->Release();
|
|
audio->initialized = false;
|
|
}
|
|
|
|
function DWORD win32_audio_thread(void *parameter) {
|
|
Audio *audio = (Audio *)parameter;
|
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
|
HANDLE buffer_ready_event = CreateEvent(0, 0, 0, 0);
|
|
if (!buffer_ready_event) {
|
|
return -1;
|
|
}
|
|
if (FAILED(w32(audio).audio_client->SetEventHandle(buffer_ready_event))) {
|
|
return -1;
|
|
}
|
|
U32 buffer_frame_count;
|
|
if (FAILED(w32(audio).audio_client->GetBufferSize(&buffer_frame_count))) {
|
|
return -1;
|
|
}
|
|
// U32 buffer_sample_count = buffer_frame_count * audio->number_of_channels;
|
|
if (FAILED(w32(audio).audio_client->Start())) {
|
|
w32(audio).audio_client->Stop();
|
|
return -1;
|
|
}
|
|
for (;;) {
|
|
if (WaitForSingleObject(buffer_ready_event, INFINITE) != WAIT_OBJECT_0) {
|
|
w32(audio).audio_client->Stop();
|
|
return -1;
|
|
}
|
|
U32 padding_frame_count;
|
|
if (FAILED(w32(audio).audio_client->GetCurrentPadding(&padding_frame_count))) {
|
|
w32(audio).audio_client->Stop();
|
|
return -1;
|
|
}
|
|
U32 *samples;
|
|
U32 fill_frame_count = buffer_frame_count - padding_frame_count;
|
|
if (FAILED(w32(audio).audio_render_client->GetBuffer(fill_frame_count, (BYTE **)&samples))) {
|
|
w32(audio).audio_client->Stop();
|
|
return -1;
|
|
}
|
|
audio->callback(audio, samples, fill_frame_count);
|
|
if (FAILED(w32(audio).audio_render_client->ReleaseBuffer(fill_frame_count, 0))) {
|
|
w32(audio).audio_client->Stop();
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function AUDIO_CALLBACK(default_audio_callback) {
|
|
memory_zero(buffer, (U64)frames_to_fill * (U64)audio->samples_per_second);
|
|
}
|
|
|
|
api B32 os_init_audio(Audio *audio) {
|
|
audio->bits_per_sample = 16;
|
|
if (audio->number_of_channels == 0)
|
|
audio->number_of_channels = 2;
|
|
if (audio->samples_per_second == 0)
|
|
audio->samples_per_second = 44100;
|
|
if (audio->callback == 0)
|
|
audio->callback = default_audio_callback;
|
|
|
|
B32 success = win32_load_com();
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
if (FAILED(CoInitializeExFunctionPointer(0, COINITBASE_MULTITHREADED))) {
|
|
log_error("Failed to initialize COM, CoInitializeEx");
|
|
return false;
|
|
}
|
|
if (FAILED(CoCreateInstanceFunctionPointer(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
|
IID_IMMDeviceEnumerator, (LPVOID *)&w32(audio).device_enum))) {
|
|
log_error("Failed to initialize COM, CoCreateInstance");
|
|
return false;
|
|
}
|
|
if (FAILED(w32(audio).device_enum->GetDefaultAudioEndpoint(eRender, eConsole, &w32(audio).device))) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, GetDefaultAudioEndpoint");
|
|
return false;
|
|
}
|
|
if (FAILED(
|
|
w32(audio).device->Activate(IID_IAudioClient, CLSCTX_ALL, 0, (void **)&w32(audio).audio_client))) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, "
|
|
"w32(audio).device->Activate(IID_IAudioClient,");
|
|
return false;
|
|
}
|
|
|
|
WAVEFORMATEX waveFormat = {};
|
|
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
waveFormat.nChannels = audio->number_of_channels;
|
|
waveFormat.nSamplesPerSec = audio->samples_per_second;
|
|
waveFormat.wBitsPerSample = audio->bits_per_sample;
|
|
waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
|
|
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
|
|
REFERENCE_TIME requestedBufferDuration = REF_TIMES_PER_MSECOND * 40;
|
|
if (FAILED(w32(audio).audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
|
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_RATEADJUST |
|
|
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
|
|
requestedBufferDuration, 0, &waveFormat, 0))) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, w32(audio).audio_client->Initialize");
|
|
return false;
|
|
}
|
|
if (FAILED(w32(audio).audio_client->GetService(IID_IAudioRenderClient,
|
|
(void **)&w32(audio).audio_render_client))) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, "
|
|
"w32(audio).audio_client->GetService(IID_IAudioRenderClient");
|
|
return false;
|
|
}
|
|
if (FAILED(w32(audio).audio_client->GetBufferSize(&audio->buffer_frame_count))) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, w32(audio).audio_client->GetBufferSize");
|
|
return false;
|
|
}
|
|
HANDLE thread_handle = CreateThread(0, 0, win32_audio_thread, audio, 0, 0);
|
|
if (!thread_handle) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, CreateThread returned 0 in handle");
|
|
return false;
|
|
}
|
|
if (thread_handle == INVALID_HANDLE_VALUE) {
|
|
os_clean_audio(audio);
|
|
log_error("Failed to initialize WASAPI, CreateThread for "
|
|
"audio failed with INVALID HANDLE VALUE");
|
|
return false;
|
|
}
|
|
CloseHandle(thread_handle);
|
|
audio->initialized = true;
|
|
#undef w32
|
|
return true;
|
|
}
|
|
|