#ifndef MU_HEADER #define MU_HEADER #include #include #include #ifndef MU_API #ifdef __cplusplus #define MU_API extern "C" #else #define MU_API #endif #endif #ifndef MU_FN #if defined(__GNUC__) || defined(__clang__) #define MU_FN __attribute__((unused)) static #else #define MU_FN static #endif #endif #ifndef MU_PRIVATE_VAR #define MU_PRIVATE_VAR MU_FN #endif #ifndef MU_INLINE #ifndef _MSC_VER #ifdef __cplusplus #define MU_INLINE inline #else #define MU_INLINE #endif #else #define MU_INLINE __forceinline #endif #endif #ifndef MU_Float2 #define MU_Float2 MU__Float2 typedef struct MU__Float2 { float x; float y; } MU__Float2; #endif #ifndef MU_Int2 #define MU_Int2 MU__Int2 typedef struct MU__Int2 { int x; int y; } MU__Int2; #endif //@begin gen_structs typedef struct MU_UTF32Result MU_UTF32Result; typedef struct MU_UTF8Result MU_UTF8Result; typedef struct MU_Win32 MU_Win32; typedef struct MU_Win32_Window MU_Win32_Window; typedef struct MU_Window_Params MU_Window_Params; typedef struct MU_Params MU_Params; typedef struct MU_Key_State MU_Key_State; typedef enum MU_Key MU_Key; typedef struct MU_Mouse_State MU_Mouse_State; typedef struct MU_DroppedFile MU_DroppedFile; typedef struct MU_Arena MU_Arena; typedef struct MU_Window MU_Window; typedef struct MU_Time MU_Time; typedef struct MU_Sound MU_Sound; typedef struct MU_Context MU_Context; //@end gen_structs typedef void *MU_glGetProcAddress(const char *); struct MU_Window_Params { MU_Int2 size; MU_Int2 pos; char *title; bool enable_canvas; bool resizable; bool borderless; bool fps_cursor; }; struct MU_Params { void *memory; size_t cap; bool enable_opengl; int opengl_major; int opengl_minor; double delta_time; MU_Window_Params window; // this controls window when calling MU_Start void (*sound_callback)(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill); }; struct MU_Key_State { bool down; bool press; bool unpress; bool raw_press; }; enum MU_Key { MU_KEY_INVALID, MU_KEY_ESCAPE, MU_KEY_ENTER, MU_KEY_TAB, MU_KEY_BACKSPACE, MU_KEY_INSERT, MU_KEY_DELETE, MU_KEY_RIGHT, MU_KEY_LEFT, MU_KEY_DOWN, MU_KEY_UP, MU_KEY_PAGE_UP, MU_KEY_PAGE_DOWN, MU_KEY_HOME, MU_KEY_END, MU_KEY_F1, MU_KEY_F2, MU_KEY_F3, MU_KEY_F4, MU_KEY_F5, MU_KEY_F6, MU_KEY_F7, MU_KEY_F8, MU_KEY_F9, MU_KEY_F10, MU_KEY_F11, MU_KEY_F12, MU_KEY_SPACE = 32, MU_KEY_APOSTROPHE = 39, MU_KEY_PLUS = 43, MU_KEY_COMMA = 44, MU_KEY_MINUS = 45, MU_KEY_PERIOD = 46, MU_KEY_SLASH = 47, MU_KEY_0 = 48, MU_KEY_1 = 49, MU_KEY_2 = 50, MU_KEY_3 = 51, MU_KEY_4 = 52, MU_KEY_5 = 53, MU_KEY_6 = 54, MU_KEY_7 = 55, MU_KEY_8 = 56, MU_KEY_9 = 57, MU_KEY_SEMICOLON = 59, MU_KEY_EQUAL = 61, MU_KEY_A = 65, MU_KEY_B = 66, MU_KEY_C = 67, MU_KEY_D = 68, MU_KEY_E = 69, MU_KEY_F = 70, MU_KEY_G = 71, MU_KEY_H = 72, MU_KEY_I = 73, MU_KEY_J = 74, MU_KEY_K = 75, MU_KEY_L = 76, MU_KEY_M = 77, MU_KEY_N = 78, MU_KEY_O = 79, MU_KEY_P = 80, MU_KEY_Q = 81, MU_KEY_R = 82, MU_KEY_S = 83, MU_KEY_T = 84, MU_KEY_U = 85, MU_KEY_V = 86, MU_KEY_W = 87, MU_KEY_X = 88, MU_KEY_Y = 89, MU_KEY_Z = 90, MU_KEY_LEFT_BRACKET = 91, MU_KEY_BACKSLASH = 92, MU_KEY_RIGHT_BRACKET = 93, MU_KEY_GRAVE_ACCENT = 96, MU_KEY_F13, MU_KEY_F14, MU_KEY_F15, MU_KEY_F16, MU_KEY_F17, MU_KEY_F18, MU_KEY_F19, MU_KEY_F20, MU_KEY_F21, MU_KEY_F22, MU_KEY_F23, MU_KEY_F24, MU_KEY_KP_0, MU_KEY_KP_1, MU_KEY_KP_2, MU_KEY_KP_3, MU_KEY_KP_4, MU_KEY_KP_5, MU_KEY_KP_6, MU_KEY_KP_7, MU_KEY_KP_8, MU_KEY_KP_9, MU_KEY_KP_DECIMAL, MU_KEY_KP_DIVIDE, MU_KEY_KP_MULTIPLY, MU_KEY_KP_SUBTRACT, MU_KEY_KP_ADD, MU_KEY_KP_ENTER, MU_KEY_LEFT_SHIFT, MU_KEY_LEFT_CONTROL, MU_KEY_LEFT_ALT, MU_KEY_LEFT_SUPER, MU_KEY_RIGHT_SHIFT, MU_KEY_RIGHT_CONTROL, MU_KEY_RIGHT_ALT, MU_KEY_RIGHT_SUPER, MU_KEY_CAPS_LOCK, MU_KEY_SCROLL_LOCK, MU_KEY_NUM_LOCK, MU_KEY_PRINT_SCREEN, MU_KEY_PAUSE, MU_KEY_SHIFT, MU_KEY_CONTROL, MU_KEY_COUNT, }; struct MU_Mouse_State { MU_Int2 pos; MU_Float2 posf; MU_Int2 delta_pos; MU_Float2 delta_pos_normalized; MU_Key_State left; MU_Key_State middle; MU_Key_State right; float delta_wheel; // @todo: add smooth delta? }; struct MU_DroppedFile { MU_DroppedFile *next; char *filename; // null terminated int filename_size; }; struct MU_Arena { char *memory; size_t len; size_t cap; }; // Most of the fields in the window struct are read only. They are updated // in appropriate update functions. The window should belong to the MU_Context // but you get access to the information. struct MU_Window { MU_Int2 size; MU_Float2 sizef; MU_Int2 pos; MU_Float2 posf; float dpi_scale; bool is_fullscreen; bool is_fps_mode; bool is_focused; bool change_cursor_on_mouse_hold; // @in @out uint64_t processed_events_this_frame; bool should_render; // @in @out this is false on first frame but it doesn't matter cause it shouldnt be rendered MU_DroppedFile *first_dropped_file; uint32_t *canvas; bool canvas_enabled; // @in @out MU_Mouse_State mouse; MU_Key_State key[MU_KEY_COUNT]; uint32_t user_text32[32]; int user_text32_count; char user_text8[32]; int user_text8_count; MU_Window *next; void *handle; void *platform; }; struct MU_Time { double app_start; double frame_start; double update; double update_total; double delta; float deltaf; double total; float totalf; }; struct MU_Sound { bool initialized; unsigned samples_per_second; unsigned number_of_channels; unsigned bytes_per_sample; void (*callback)(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill); }; struct MU_Context { bool quit; MU_Sound sound; MU_Time time; bool first_frame; int _MU_Update_count; size_t frame; size_t consecutive_missed_frames; size_t total_missed_frames; MU_Int2 primary_monitor_size; bool opengl_initialized; int opengl_major; int opengl_minor; void *(*gl_get_proc_address)(const char *str); MU_Params params; MU_Window *window; MU_Window *all_windows; MU_Arena perm_arena; MU_Arena frame_arena; // Reset at beginning of MU_Update void *platform; }; //@begin gen_api_funcs MU_API void MU_Quit(MU_Context *mu); MU_API void MU_DefaultSoundCallback(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill); MU_API double MU_GetTime(void); MU_API void MU_ToggleFPSMode(MU_Window *window); MU_API void MU_DisableFPSMode(MU_Window *window); MU_API void MU_EnableFPSMode(MU_Window *window); MU_API void MU_ToggleFullscreen(MU_Window *window); MU_API void MU_Init(MU_Context *mu, MU_Params params, size_t len); MU_API MU_Window *MU_AddWindow(MU_Context *mu, MU_Window_Params params); MU_API void MU_InitWindow(MU_Context *mu, MU_Window *window, MU_Window_Params params); MU_API MU_Context *MU_Start(MU_Params params); MU_API bool MU_Update(MU_Context *mu); //@end gen_api_funcs /* @! In the future, api for processing messages manually while(true) { MU_Event event; while (mu_get_event_blocking(&event)) { switch(event.kind) { } } } typedef int MU_Modifier; enum MU_Modifier { MU_MODIFIER_SHIFT = 0x1, // left or right shift key MU_MODIFIER_CTRL = 0x2, // left or right control key MU_MODIFIER_ALT = 0x4, // left or right alt key MU_MODIFIER_SUPER = 0x8, // left or right 'super' key MU_MODIFIER_LMB = 0x100, // left mouse button MU_MODIFIER_RMB = 0x200, // right mouse button MU_MODIFIER_MMB = 0x400, // middle mouse button }; typedef enum MU_Event_Kind { MU_EVENT_KIND_INVALID, MU_EVENT_KIND_KEY_DOWN, MU_EVENT_KIND_KEY_UP, MU_EVENT_KIND_MOUSE_MOVE, } MU_Event_Kind; typedef struct MU_Event { MU_Event_Kind kind; MU_Modifier modifier; MU_Key key; } MU_Event; */ #endif // MU_HEADER #ifdef MU_IMPLEMENTATION #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include // for handling dropped files #define INITGUID #define CINTERFACE #define COBJMACROS #define CONST_VTABLE #include #include #include #include // // Automatically linking with the libraries // #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "shell32.lib") // For handling dropping files into the app #endif MU_FN void *MU_PushSize(MU_Arena *ar, size_t size); MU_FN MU_UTF32Result MU_UTF16ToUTF32(uint16_t *c, int max_advance); MU_FN MU_UTF8Result MU_UTF32ToUTF8(uint32_t codepoint); MU_FN int64_t MU_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen); MU_FN void MU_WIN32_UpdateFocusedWindowBasedOnHandle(MU_Context *mu, HWND handle); MU_FN void MU_WIN32_UpdateFocusedWindow(MU_Context *mu); MU_FN void MU_Win32_GetWindowSize(HWND window, int *x, int *y); MU_FN void MU_WIN32_GetWindowPos(HWND window, int *x, int *y); MU_FN MU_Int2 MU_WIN32_GetMousePosition(HWND window); MU_FN MU_Int2 MU_WIN32_GetMousePositionInverted(HWND window, int y); MU_FN void MU_WIN32_CreateCanvas(MU_Window *window); MU_FN void MU_WIN32_DestroyCanvas(MU_Window *window); MU_FN void MU_WIN32_DrawCanvas(MU_Window *window); MU_FN void MU__MemoryCopy(void *dst, const void *src, size_t size); MU_FN void MU_PushDroppedFile(MU_Context *mu, MU_Window *window, char *str, int size); MU_FN void MU_UpdateWindowState(MU_Window *window); MU_FN int MU__AreStringsEqual(const char *src, const char *dst, size_t dstlen); MU_FN void *MU_Win32_GLGetWindowProcAddressForGlad(const char *proc); MU_FN void MU_WIN32_GetWGLFunctions(MU_Context *mu); MU_FN void MU_WIN32_TryToInitGLContextForWindow(MU_Context *mu, MU_Win32_Window *w32_window); MU_FN void MU_WIN32_DeinitSound(MU_Context *mu); MU_FN void MU_WIN32_LoadCOM(MU_Context *mu); MU_FN DWORD MU_WIN32_SoundThread(void *parameter); MU_FN void MU_WIN32_InitWasapi(MU_Context *mu); #ifndef MU_GL_ENABLE_MULTISAMPLING #define MU_GL_ENABLE_MULTISAMPLING 1 #endif #ifndef MU_GL_BUILD_DEBUG #define MU_GL_BUILD_DEBUG 1 #endif #ifndef MU_ASSERT_CODE #define MU_ASSERT_CODE(x) x #endif #ifndef MU_ASSERT #include #define MU_ASSERT(x) assert(x) #endif /* Quake uses this to sleep when user is not interacting with app void SleepUntilInput (int time) { MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT); } if ((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing) { SleepUntilInput (PAUSE_SLEEP); scr_skipupdate = 1; // no point in bothering to draw } else if (!ActiveApp && !DDActive) { SleepUntilInput (NOT_FOCUS_SLEEP); } */ // @! Add native handle to MU_Context for Directx 11 initialize // @! Add option to manually blit, some manual blit param and manual blit function // @! Add ram info? https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex // @! Maybe make the library friendly to people who dont use debuggers // @! Set window title // @! Good defaults for multiple windows ?? struct MU_UTF32Result { uint32_t out_str; int advance; int error; }; struct MU_UTF8Result { char out_str[4]; int len; int error; }; #define MU_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define MU_STACK_ADD_MOD(stack_base, new_stack_base, next) \ do { \ (new_stack_base)->next = (stack_base); \ (stack_base) = (new_stack_base); \ } while (0) #define MU_STACK_ADD(stack_base, new_stack_base) \ MU_STACK_ADD_MOD(stack_base, new_stack_base, next) MU_API void MU_Quit(MU_Context *mu) { mu->quit = true; } MU_API void MU_DefaultSoundCallback(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill) { } MU_INLINE void MU__WriteChar8(MU_Window *window, char *c, int len) { if (window->user_text8_count + len < MU_ARRAY_SIZE(window->user_text8)) { for (int i = 0; i < len; i += 1) { window->user_text8[window->user_text8_count++] = c[i]; } } } MU_INLINE void MU__WriteChar32(MU_Window *window, uint32_t c) { if (window->user_text32_count + 1 < MU_ARRAY_SIZE(window->user_text32)) { window->user_text32[window->user_text32_count++] = c; } } MU_INLINE void MU__KeyDown(MU_Window *window, MU_Key key) { if (window->key[key].down == false) window->key[key].press = true; window->key[key].down = true; window->key[key].raw_press = true; } MU_INLINE void MU__ZeroMemory(void *p, size_t size) { uint8_t *p8 = (uint8_t *)p; for (size_t i = 0; i < size; i += 1) { p8[i] = 0; } } MU_INLINE size_t MU__GetAlignOffset(size_t size, size_t align) { size_t mask = align - 1; size_t val = size & mask; if (val) { val = align - val; } return val; } MU_INLINE void MU__KeyUP(MU_Window *window, MU_Key key) { if (window->key[key].down == true) window->key[key].unpress = true; window->key[key].down = false; } MU_INLINE bool MU_DoesSizeFit(MU_Arena *ar, size_t size) { const size_t alignment = 16; size_t align = MU__GetAlignOffset((uintptr_t)ar->memory + ar->len, alignment); size_t cursor = ar->len + align; bool result = cursor + size <= ar->cap; return result; } #define MU_PUSH_STRUCT(mu, T) (T *)MU_PushSize(mu, sizeof(T)) MU_FN void *MU_PushSize(MU_Arena *ar, size_t size) { const size_t alignment = 16; ar->len += MU__GetAlignOffset((uintptr_t)ar->memory + ar->len, alignment); if (ar->len + size > ar->cap) { MU_ASSERT(!"MU_Context has not enough memory for what you are trying to do!"); } void *result = (void *)(ar->memory + ar->len); ar->len += size; MU_ASSERT(ar->len < ar->cap); MU__ZeroMemory(result, size); return result; } MU_FN MU_UTF32Result MU_UTF16ToUTF32(uint16_t *c, int max_advance) { MU_UTF32Result result; MU__ZeroMemory(&result, sizeof(result)); if (max_advance >= 1) { result.advance = 1; result.out_str = c[0]; if (c[0] >= 0xD800 && c[0] <= 0xDBFF && c[1] >= 0xDC00 && c[1] <= 0xDFFF) { if (max_advance >= 2) { result.out_str = 0x10000; result.out_str += (uint32_t)(c[0] & 0x03FF) << 10u | (c[1] & 0x03FF); result.advance = 2; } else result.error = 2; } } else { result.error = 1; } return result; } MU_FN MU_UTF8Result MU_UTF32ToUTF8(uint32_t codepoint) { MU_UTF8Result result; MU__ZeroMemory(&result, sizeof(result)); if (codepoint <= 0x7F) { result.len = 1; result.out_str[0] = (char)codepoint; } else if (codepoint <= 0x7FF) { result.len = 2; result.out_str[0] = 0xc0 | (0x1f & (codepoint >> 6)); result.out_str[1] = 0x80 | (0x3f & codepoint); } else if (codepoint <= 0xFFFF) { // 16 bit word result.len = 3; result.out_str[0] = 0xe0 | (0xf & (codepoint >> 12)); // 4 bits result.out_str[1] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits result.out_str[2] = 0x80 | (0x3f & codepoint); // 6 bits } else if (codepoint <= 0x10FFFF) { // 21 bit word result.len = 4; result.out_str[0] = 0xf0 | (0x7 & (codepoint >> 18)); // 3 bits result.out_str[1] = 0x80 | (0x3f & (codepoint >> 12)); // 6 bits result.out_str[2] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits result.out_str[3] = 0x80 | (0x3f & codepoint); // 6 bits } else { result.error = true; } return result; } // @warning: this function is a little different from usual, returns -1 on decode errors MU_FN int64_t MU_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen) { int64_t outlen = 0; for (int64_t i = 0; i < inlen && in[i] != 0;) { MU_UTF32Result decode = MU_UTF16ToUTF32((uint16_t *)(in + i), (int)(inlen - i)); if (!decode.error) { i += decode.advance; MU_UTF8Result encode = MU_UTF32ToUTF8(decode.out_str); if (!encode.error) { for (int64_t j = 0; j < encode.len; j++) { if (outlen >= buffer_size) { outlen = -1; goto failed_to_decode; } buffer[outlen++] = encode.out_str[j]; } } else { outlen = -1; goto failed_to_decode; } } else { outlen = -1; goto failed_to_decode; } } buffer[outlen] = 0; failed_to_decode:; return outlen; } #ifdef _WIN32 #define MU_DEFAULT_MEMORY_SIZE (1024 * 4) // Typedefines for the COM functions which are going to be loaded currently only required for sound typedef HRESULT CoCreateInstanceFunction(REFCLSID rclsid, LPUNKNOWN *pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); typedef HRESULT CoInitializeExFunction(LPVOID pvReserved, DWORD dwCoInit); struct MU_Win32 { WNDCLASSW wc; bool good_scheduling; MU_glGetProcAddress *wgl_get_proc_address; HMODULE opengl32; HCURSOR cursor_hand; HCURSOR cursor_arrow; // Sound IMMDevice *device; IAudioClient *audio_client; IMMDeviceEnumerator *device_enum; IAudioRenderClient *audio_render_client; uint32_t buffer_frame_count; // IAudioCaptureClient *audio_capture_client; // Pointers to the functions from the dll CoCreateInstanceFunction *CoCreateInstanceFunctionPointer; CoInitializeExFunction *CoInitializeExFunctionPointer; }; struct MU_Win32_Window { HDC handle_dc; HDC canvas_dc; HBITMAP canvas_dib; // for fullscreen WINDOWPLACEMENT prev_placement; DWORD style; }; MU_API double MU_GetTime(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); double result = (double)time.QuadPart / (double)counts_per_second; return result; } MU_FN void MU_WIN32_UpdateFocusedWindowBasedOnHandle(MU_Context *mu, HWND handle) { if (mu->all_windows == 0) return; for (MU_Window *it = mu->all_windows; it; it = it->next) { if (it->handle == handle) { mu->window = it; return; } } } MU_FN void MU_WIN32_UpdateFocusedWindow(MU_Context *mu) { HWND handle = GetFocus(); if (handle) { MU_WIN32_UpdateFocusedWindowBasedOnHandle(mu, handle); } } MU_FN void MU_Win32_GetWindowSize(HWND window, int *x, int *y) { RECT window_rect; GetClientRect(window, &window_rect); *x = window_rect.right - window_rect.left; *y = window_rect.bottom - window_rect.top; } MU_FN void MU_WIN32_GetWindowPos(HWND window, int *x, int *y) { POINT point = {0, 0}; ClientToScreen(window, &point); *x = point.x; *y = point.y; } MU_FN MU_Int2 MU_WIN32_GetMousePosition(HWND window) { POINT p; GetCursorPos(&p); ScreenToClient(window, &p); MU_Int2 result = {p.x, p.y}; return result; } MU_FN MU_Int2 MU_WIN32_GetMousePositionInverted(HWND window, int y) { MU_Int2 result = MU_WIN32_GetMousePosition(window); result.y = y - result.y; return result; } MU_FN void MU_WIN32_CreateCanvas(MU_Window *window) { MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; MU_ASSERT(window->canvas == 0); BITMAPINFO bminfo; { MU__ZeroMemory(&bminfo, sizeof(bminfo)); 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; } w32_window->canvas_dib = CreateDIBSection(w32_window->handle_dc, &bminfo, DIB_RGB_COLORS, (void **)&window->canvas, 0, 0); w32_window->canvas_dc = CreateCompatibleDC(w32_window->handle_dc); } MU_FN void MU_WIN32_DestroyCanvas(MU_Window *window) { MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; if (window->canvas) { DeleteDC(w32_window->canvas_dc); DeleteObject(w32_window->canvas_dib); w32_window->canvas_dc = 0; w32_window->canvas_dib = 0; window->canvas = 0; } } MU_FN void MU_WIN32_DrawCanvas(MU_Window *window) { if (window->canvas) { MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; SelectObject(w32_window->canvas_dc, w32_window->canvas_dib); BitBlt(w32_window->handle_dc, 0, 0, window->size.x, window->size.y, w32_window->canvas_dc, 0, 0, SRCCOPY); } } MU_API void MU_ToggleFPSMode(MU_Window *window) { ShowCursor(window->is_fps_mode); window->is_fps_mode = !window->is_fps_mode; } MU_API void MU_DisableFPSMode(MU_Window *window) { if (window->is_fps_mode == true) MU_ToggleFPSMode(window); } MU_API void MU_EnableFPSMode(MU_Window *window) { if (window->is_fps_mode == false) MU_ToggleFPSMode(window); } MU_FN void MU__MemoryCopy(void *dst, const void *src, size_t size) { char *src8 = (char *)src; char *dst8 = (char *)dst; while (size--) *dst8++ = *src8++; } // https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353 MU_API void MU_ToggleFullscreen(MU_Window *window) { MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; DWORD dwStyle = GetWindowLong((HWND)window->handle, GWL_STYLE); if (window->is_fullscreen == false) { MONITORINFO mi = {sizeof(mi)}; if (GetWindowPlacement((HWND)window->handle, &w32_window->prev_placement) && GetMonitorInfo(MonitorFromWindow((HWND)window->handle, MONITOR_DEFAULTTOPRIMARY), &mi)) { SetWindowLong((HWND)window->handle, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW); BOOL result = SetWindowPos((HWND)window->handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); if (result) window->is_fullscreen = true; } } else { SetWindowLong((HWND)window->handle, GWL_STYLE, w32_window->style); SetWindowPlacement((HWND)window->handle, &w32_window->prev_placement); BOOL result = SetWindowPos((HWND)window->handle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); if (result) window->is_fullscreen = false; } } MU_FN void MU_PushDroppedFile(MU_Context *mu, MU_Window *window, char *str, int size) { if (MU_DoesSizeFit(&mu->frame_arena, sizeof(MU_DroppedFile) + size)) { MU_DroppedFile *result = MU_PUSH_STRUCT(&mu->frame_arena, MU_DroppedFile); result->filename = (char *)MU_PushSize(&mu->frame_arena, size + 1); result->filename_size = size; MU__MemoryCopy(result->filename, str, size); result->filename[size] = 0; result->next = window->first_dropped_file; window->first_dropped_file = result; } } // Should be initialized before processing events // Should be initialized before initializing opengl functions using GLAD static MU_Context *MU_WIN32_ContextPointerForEventHandling = 0; static LRESULT CALLBACK MU_WIN32_WindowProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) { MU_Context *mu = MU_WIN32_ContextPointerForEventHandling; MU_Win32 *w32 = (MU_Win32 *)mu->platform; MU_WIN32_UpdateFocusedWindowBasedOnHandle(mu, wnd); MU_Window *window = mu->window ? mu->window : 0; if (window) window->processed_events_this_frame += 1; (void)w32; switch (msg) { case WM_DROPFILES: { wchar_t buffer[512]; char buffer8[1024]; HDROP hdrop = (HDROP)wparam; int file_count = (int)DragQueryFileW(hdrop, 0xffffffff, NULL, 0); bool drop_failed = false; for (int i = 0; i < file_count; i += 1) { // UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; // WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); UINT result = DragQueryFileW(hdrop, i, buffer, MU_ARRAY_SIZE(buffer)); MU_ASSERT(result != 0); if (result) { int64_t size = MU_CreateCharFromWidechar(buffer8, MU_ARRAY_SIZE(buffer8), buffer, MU_ARRAY_SIZE(buffer)); if (size != -1) { MU_PushDroppedFile(mu, window, buffer8, (int)size); } } } DragFinish(hdrop); } break; case WM_CLOSE: { // @! Make sure that focus works // @! We should find the window and make sure we inform it that the user clicked the close button PostQuitMessage(0); mu->quit = true; } break; case WM_LBUTTONDOWN: { SetCapture(wnd); if (window->change_cursor_on_mouse_hold) SetCursor(w32->cursor_hand); if (window->mouse.left.down == false) window->mouse.left.press = true; window->mouse.left.down = true; } break; case WM_LBUTTONUP: { ReleaseCapture(); if (window->change_cursor_on_mouse_hold) SetCursor(w32->cursor_arrow); if (window->mouse.left.down == true) window->mouse.left.unpress = true; window->mouse.left.down = false; } break; case WM_RBUTTONDOWN: { SetCapture(wnd); if (window->mouse.right.down == false) window->mouse.right.press = true; window->mouse.right.down = true; } break; case WM_RBUTTONUP: { ReleaseCapture(); if (window->mouse.right.down == true) window->mouse.right.unpress = true; window->mouse.right.down = false; } break; case WM_MBUTTONDOWN: { SetCapture(wnd); if (window->mouse.middle.down == false) window->mouse.middle.press = true; window->mouse.middle.down = true; } break; case WM_MBUTTONUP: { ReleaseCapture(); if (window->mouse.middle.down == true) window->mouse.middle.unpress = true; window->mouse.middle.down = false; } break; case WM_MOUSEWHEEL: { if ((int)wparam > 0) { window->mouse.delta_wheel += 1.0f; } else { window->mouse.delta_wheel -= 1.0f; } } break; case WM_CHAR: { MU_UTF32Result encode = MU_UTF16ToUTF32((uint16_t *)&wparam, 2); if (encode.error) { MU__WriteChar32(window, (uint32_t)'?'); MU__WriteChar8(window, "?", 1); } else { MU__WriteChar32(window, encode.out_str); } // Utf8 encode if (encode.error == false) { MU_UTF8Result encode8 = MU_UTF32ToUTF8(encode.out_str); if (encode8.error) { MU__WriteChar8(window, "?", 1); } else { MU__WriteChar8(window, encode8.out_str, encode8.len); } } } break; case WM_KEYUP: case WM_SYSKEYUP: { switch (wparam) { case VK_ESCAPE: MU__KeyUP(window, MU_KEY_ESCAPE); break; case VK_RETURN: MU__KeyUP(window, MU_KEY_ENTER); break; case VK_TAB: MU__KeyUP(window, MU_KEY_TAB); break; case VK_BACK: MU__KeyUP(window, MU_KEY_BACKSPACE); break; case VK_INSERT: MU__KeyUP(window, MU_KEY_INSERT); break; case VK_DELETE: MU__KeyUP(window, MU_KEY_DELETE); break; case VK_RIGHT: MU__KeyUP(window, MU_KEY_RIGHT); break; case VK_LEFT: MU__KeyUP(window, MU_KEY_LEFT); break; case VK_DOWN: MU__KeyUP(window, MU_KEY_DOWN); break; case VK_UP: MU__KeyUP(window, MU_KEY_UP); break; case VK_PRIOR: MU__KeyUP(window, MU_KEY_PAGE_UP); break; case VK_NEXT: MU__KeyUP(window, MU_KEY_PAGE_DOWN); break; case VK_END: MU__KeyUP(window, MU_KEY_HOME); break; case VK_HOME: MU__KeyUP(window, MU_KEY_END); break; case VK_F1: MU__KeyUP(window, MU_KEY_F1); break; case VK_F2: MU__KeyUP(window, MU_KEY_F2); break; case VK_F3: MU__KeyUP(window, MU_KEY_F3); break; case VK_F4: MU__KeyUP(window, MU_KEY_F4); break; case VK_F5: MU__KeyUP(window, MU_KEY_F5); break; case VK_F6: MU__KeyUP(window, MU_KEY_F6); break; case VK_F7: MU__KeyUP(window, MU_KEY_F7); break; case VK_F8: MU__KeyUP(window, MU_KEY_F8); break; case VK_F9: MU__KeyUP(window, MU_KEY_F9); break; case VK_F10: MU__KeyUP(window, MU_KEY_F10); break; case VK_F11: MU__KeyUP(window, MU_KEY_F11); break; case VK_F12: MU__KeyUP(window, MU_KEY_F12); break; case VK_SPACE: MU__KeyUP(window, MU_KEY_SPACE); break; case VK_OEM_PLUS: MU__KeyUP(window, MU_KEY_PLUS); break; case VK_OEM_COMMA: MU__KeyUP(window, MU_KEY_COMMA); break; case VK_OEM_MINUS: MU__KeyUP(window, MU_KEY_MINUS); break; case VK_OEM_PERIOD: MU__KeyUP(window, MU_KEY_PERIOD); break; case '0': MU__KeyUP(window, MU_KEY_0); break; case '1': MU__KeyUP(window, MU_KEY_1); break; case '2': MU__KeyUP(window, MU_KEY_2); break; case '3': MU__KeyUP(window, MU_KEY_3); break; case '4': MU__KeyUP(window, MU_KEY_4); break; case '5': MU__KeyUP(window, MU_KEY_5); break; case '6': MU__KeyUP(window, MU_KEY_6); break; case '7': MU__KeyUP(window, MU_KEY_7); break; case '8': MU__KeyUP(window, MU_KEY_8); break; case '9': MU__KeyUP(window, MU_KEY_9); break; case ';': MU__KeyUP(window, MU_KEY_SEMICOLON); break; case '=': MU__KeyUP(window, MU_KEY_EQUAL); break; case 'A': MU__KeyUP(window, MU_KEY_A); break; case 'B': MU__KeyUP(window, MU_KEY_B); break; case 'C': MU__KeyUP(window, MU_KEY_C); break; case 'D': MU__KeyUP(window, MU_KEY_D); break; case 'E': MU__KeyUP(window, MU_KEY_E); break; case 'F': MU__KeyUP(window, MU_KEY_F); break; case 'G': MU__KeyUP(window, MU_KEY_G); break; case 'H': MU__KeyUP(window, MU_KEY_H); break; case 'I': MU__KeyUP(window, MU_KEY_I); break; case 'J': MU__KeyUP(window, MU_KEY_J); break; case 'K': MU__KeyUP(window, MU_KEY_K); break; case 'L': MU__KeyUP(window, MU_KEY_L); break; case 'M': MU__KeyUP(window, MU_KEY_M); break; case 'N': MU__KeyUP(window, MU_KEY_N); break; case 'O': MU__KeyUP(window, MU_KEY_O); break; case 'P': MU__KeyUP(window, MU_KEY_P); break; case 'Q': MU__KeyUP(window, MU_KEY_Q); break; case 'R': MU__KeyUP(window, MU_KEY_R); break; case 'S': MU__KeyUP(window, MU_KEY_S); break; case 'T': MU__KeyUP(window, MU_KEY_T); break; case 'U': MU__KeyUP(window, MU_KEY_U); break; case 'V': MU__KeyUP(window, MU_KEY_V); break; case 'W': MU__KeyUP(window, MU_KEY_W); break; case 'X': MU__KeyUP(window, MU_KEY_X); break; case 'Y': MU__KeyUP(window, MU_KEY_Y); break; case 'Z': MU__KeyUP(window, MU_KEY_Z); break; case VK_F13: MU__KeyUP(window, MU_KEY_F13); break; case VK_F14: MU__KeyUP(window, MU_KEY_F14); break; case VK_F15: MU__KeyUP(window, MU_KEY_F15); break; case VK_F16: MU__KeyUP(window, MU_KEY_F16); break; case VK_F17: MU__KeyUP(window, MU_KEY_F17); break; case VK_F18: MU__KeyUP(window, MU_KEY_F18); break; case VK_F19: MU__KeyUP(window, MU_KEY_F19); break; case VK_F20: MU__KeyUP(window, MU_KEY_F20); break; case VK_F21: MU__KeyUP(window, MU_KEY_F21); break; case VK_F22: MU__KeyUP(window, MU_KEY_F22); break; case VK_F23: MU__KeyUP(window, MU_KEY_F23); break; case VK_F24: MU__KeyUP(window, MU_KEY_F24); break; case 0x60: MU__KeyUP(window, MU_KEY_KP_0); break; case 0x61: MU__KeyUP(window, MU_KEY_KP_1); break; case 0x62: MU__KeyUP(window, MU_KEY_KP_2); break; case 0x63: MU__KeyUP(window, MU_KEY_KP_3); break; case 0x64: MU__KeyUP(window, MU_KEY_KP_4); break; case 0x65: MU__KeyUP(window, MU_KEY_KP_5); break; case 0x66: MU__KeyUP(window, MU_KEY_KP_6); break; case 0x67: MU__KeyUP(window, MU_KEY_KP_7); break; case 0x68: MU__KeyUP(window, MU_KEY_KP_8); break; case 0x69: MU__KeyUP(window, MU_KEY_KP_9); break; case VK_DECIMAL: MU__KeyUP(window, MU_KEY_KP_DECIMAL); break; case VK_DIVIDE: MU__KeyUP(window, MU_KEY_KP_DIVIDE); break; case VK_MULTIPLY: MU__KeyUP(window, MU_KEY_KP_MULTIPLY); break; case VK_SUBTRACT: MU__KeyUP(window, MU_KEY_KP_SUBTRACT); break; case VK_ADD: MU__KeyUP(window, MU_KEY_KP_ADD); break; case VK_LMENU: MU__KeyUP(window, MU_KEY_LEFT_ALT); break; case VK_LWIN: MU__KeyUP(window, MU_KEY_LEFT_SUPER); break; case VK_CONTROL: MU__KeyUP(window, MU_KEY_CONTROL); break; case VK_SHIFT: MU__KeyUP(window, MU_KEY_SHIFT); break; case VK_LSHIFT: MU__KeyUP(window, MU_KEY_LEFT_SHIFT); break; case VK_LCONTROL: MU__KeyUP(window, MU_KEY_LEFT_CONTROL); break; case VK_RSHIFT: MU__KeyUP(window, MU_KEY_RIGHT_SHIFT); break; case VK_RCONTROL: MU__KeyUP(window, MU_KEY_RIGHT_CONTROL); break; case VK_RMENU: MU__KeyUP(window, MU_KEY_RIGHT_ALT); break; case VK_RWIN: MU__KeyUP(window, MU_KEY_RIGHT_SUPER); break; case VK_CAPITAL: MU__KeyUP(window, MU_KEY_CAPS_LOCK); break; case VK_SCROLL: MU__KeyUP(window, MU_KEY_SCROLL_LOCK); break; case VK_NUMLOCK: MU__KeyUP(window, MU_KEY_NUM_LOCK); break; case VK_SNAPSHOT: MU__KeyUP(window, MU_KEY_PRINT_SCREEN); break; case VK_PAUSE: MU__KeyUP(window, MU_KEY_PAUSE); break; } } break; case WM_KEYDOWN: case WM_SYSKEYDOWN: { switch (wparam) { case VK_ESCAPE: MU__KeyDown(window, MU_KEY_ESCAPE); break; case VK_RETURN: MU__KeyDown(window, MU_KEY_ENTER); break; case VK_TAB: MU__KeyDown(window, MU_KEY_TAB); break; case VK_BACK: MU__KeyDown(window, MU_KEY_BACKSPACE); break; case VK_INSERT: MU__KeyDown(window, MU_KEY_INSERT); break; case VK_DELETE: MU__KeyDown(window, MU_KEY_DELETE); break; case VK_RIGHT: MU__KeyDown(window, MU_KEY_RIGHT); break; case VK_LEFT: MU__KeyDown(window, MU_KEY_LEFT); break; case VK_DOWN: MU__KeyDown(window, MU_KEY_DOWN); break; case VK_UP: MU__KeyDown(window, MU_KEY_UP); break; case VK_PRIOR: MU__KeyDown(window, MU_KEY_PAGE_UP); break; case VK_NEXT: MU__KeyDown(window, MU_KEY_PAGE_DOWN); break; case VK_END: MU__KeyDown(window, MU_KEY_HOME); break; case VK_HOME: MU__KeyDown(window, MU_KEY_END); break; case VK_F1: MU__KeyDown(window, MU_KEY_F1); break; case VK_F2: MU__KeyDown(window, MU_KEY_F2); break; case VK_F3: MU__KeyDown(window, MU_KEY_F3); break; case VK_F4: MU__KeyDown(window, MU_KEY_F4); break; case VK_F5: MU__KeyDown(window, MU_KEY_F5); break; case VK_F6: MU__KeyDown(window, MU_KEY_F6); break; case VK_F7: MU__KeyDown(window, MU_KEY_F7); break; case VK_F8: MU__KeyDown(window, MU_KEY_F8); break; case VK_F9: MU__KeyDown(window, MU_KEY_F9); break; case VK_F10: MU__KeyDown(window, MU_KEY_F10); break; case VK_F11: MU__KeyDown(window, MU_KEY_F11); break; case VK_F12: MU__KeyDown(window, MU_KEY_F12); break; case VK_SPACE: MU__KeyDown(window, MU_KEY_SPACE); break; case VK_OEM_PLUS: MU__KeyDown(window, MU_KEY_PLUS); break; case VK_OEM_COMMA: MU__KeyDown(window, MU_KEY_COMMA); break; case VK_OEM_MINUS: MU__KeyDown(window, MU_KEY_MINUS); break; case VK_OEM_PERIOD: MU__KeyDown(window, MU_KEY_PERIOD); break; case '0': MU__KeyDown(window, MU_KEY_0); break; case '1': MU__KeyDown(window, MU_KEY_1); break; case '2': MU__KeyDown(window, MU_KEY_2); break; case '3': MU__KeyDown(window, MU_KEY_3); break; case '4': MU__KeyDown(window, MU_KEY_4); break; case '5': MU__KeyDown(window, MU_KEY_5); break; case '6': MU__KeyDown(window, MU_KEY_6); break; case '7': MU__KeyDown(window, MU_KEY_7); break; case '8': MU__KeyDown(window, MU_KEY_8); break; case '9': MU__KeyDown(window, MU_KEY_9); break; case ';': MU__KeyDown(window, MU_KEY_SEMICOLON); break; case '=': MU__KeyDown(window, MU_KEY_EQUAL); break; case 'A': MU__KeyDown(window, MU_KEY_A); break; case 'B': MU__KeyDown(window, MU_KEY_B); break; case 'C': MU__KeyDown(window, MU_KEY_C); break; case 'D': MU__KeyDown(window, MU_KEY_D); break; case 'E': MU__KeyDown(window, MU_KEY_E); break; case 'F': MU__KeyDown(window, MU_KEY_F); break; case 'G': MU__KeyDown(window, MU_KEY_G); break; case 'H': MU__KeyDown(window, MU_KEY_H); break; case 'I': MU__KeyDown(window, MU_KEY_I); break; case 'J': MU__KeyDown(window, MU_KEY_J); break; case 'K': MU__KeyDown(window, MU_KEY_K); break; case 'L': MU__KeyDown(window, MU_KEY_L); break; case 'M': MU__KeyDown(window, MU_KEY_M); break; case 'N': MU__KeyDown(window, MU_KEY_N); break; case 'O': MU__KeyDown(window, MU_KEY_O); break; case 'P': MU__KeyDown(window, MU_KEY_P); break; case 'Q': MU__KeyDown(window, MU_KEY_Q); break; case 'R': MU__KeyDown(window, MU_KEY_R); break; case 'S': MU__KeyDown(window, MU_KEY_S); break; case 'T': MU__KeyDown(window, MU_KEY_T); break; case 'U': MU__KeyDown(window, MU_KEY_U); break; case 'V': MU__KeyDown(window, MU_KEY_V); break; case 'W': MU__KeyDown(window, MU_KEY_W); break; case 'X': MU__KeyDown(window, MU_KEY_X); break; case 'Y': MU__KeyDown(window, MU_KEY_Y); break; case 'Z': MU__KeyDown(window, MU_KEY_Z); break; case VK_F13: MU__KeyDown(window, MU_KEY_F13); break; case VK_F14: MU__KeyDown(window, MU_KEY_F14); break; case VK_F15: MU__KeyDown(window, MU_KEY_F15); break; case VK_F16: MU__KeyDown(window, MU_KEY_F16); break; case VK_F17: MU__KeyDown(window, MU_KEY_F17); break; case VK_F18: MU__KeyDown(window, MU_KEY_F18); break; case VK_F19: MU__KeyDown(window, MU_KEY_F19); break; case VK_F20: MU__KeyDown(window, MU_KEY_F20); break; case VK_F21: MU__KeyDown(window, MU_KEY_F21); break; case VK_F22: MU__KeyDown(window, MU_KEY_F22); break; case VK_F23: MU__KeyDown(window, MU_KEY_F23); break; case VK_F24: MU__KeyDown(window, MU_KEY_F24); break; case 0x60: MU__KeyDown(window, MU_KEY_KP_0); break; case 0x61: MU__KeyDown(window, MU_KEY_KP_1); break; case 0x62: MU__KeyDown(window, MU_KEY_KP_2); break; case 0x63: MU__KeyDown(window, MU_KEY_KP_3); break; case 0x64: MU__KeyDown(window, MU_KEY_KP_4); break; case 0x65: MU__KeyDown(window, MU_KEY_KP_5); break; case 0x66: MU__KeyDown(window, MU_KEY_KP_6); break; case 0x67: MU__KeyDown(window, MU_KEY_KP_7); break; case 0x68: MU__KeyDown(window, MU_KEY_KP_8); break; case 0x69: MU__KeyDown(window, MU_KEY_KP_9); break; case VK_CONTROL: MU__KeyDown(window, MU_KEY_CONTROL); break; case VK_SHIFT: MU__KeyDown(window, MU_KEY_SHIFT); break; case VK_DECIMAL: MU__KeyDown(window, MU_KEY_KP_DECIMAL); break; case VK_DIVIDE: MU__KeyDown(window, MU_KEY_KP_DIVIDE); break; case VK_MULTIPLY: MU__KeyDown(window, MU_KEY_KP_MULTIPLY); break; case VK_SUBTRACT: MU__KeyDown(window, MU_KEY_KP_SUBTRACT); break; case VK_ADD: MU__KeyDown(window, MU_KEY_KP_ADD); break; case VK_LSHIFT: MU__KeyDown(window, MU_KEY_LEFT_SHIFT); break; case VK_LCONTROL: MU__KeyDown(window, MU_KEY_LEFT_CONTROL); break; case VK_LMENU: MU__KeyDown(window, MU_KEY_LEFT_ALT); break; case VK_LWIN: MU__KeyDown(window, MU_KEY_LEFT_SUPER); break; case VK_RSHIFT: MU__KeyDown(window, MU_KEY_RIGHT_SHIFT); break; case VK_RCONTROL: MU__KeyDown(window, MU_KEY_RIGHT_CONTROL); break; case VK_RMENU: MU__KeyDown(window, MU_KEY_RIGHT_ALT); break; case VK_RWIN: MU__KeyDown(window, MU_KEY_RIGHT_SUPER); break; case VK_CAPITAL: MU__KeyDown(window, MU_KEY_CAPS_LOCK); break; case VK_SCROLL: MU__KeyDown(window, MU_KEY_SCROLL_LOCK); break; case VK_NUMLOCK: MU__KeyDown(window, MU_KEY_NUM_LOCK); break; case VK_SNAPSHOT: MU__KeyDown(window, MU_KEY_PRINT_SCREEN); break; case VK_PAUSE: MU__KeyDown(window, MU_KEY_PAUSE); break; } } break; default: { return DefWindowProcW(wnd, msg, wparam, lparam); } } return 0; } MU_API void MU_Init(MU_Context *mu, MU_Params params, size_t len) { MU_ASSERT(params.memory && params.cap && "Expected any kind of memory"); MU__ZeroMemory(mu, sizeof(*mu)); mu->perm_arena.memory = (char *)params.memory; mu->perm_arena.cap = params.cap; mu->perm_arena.len = len; MU_WIN32_ContextPointerForEventHandling = mu; mu->platform = MU_PUSH_STRUCT(&mu->perm_arena, MU_Win32); MU_Win32 *w32 = (MU_Win32 *)mu->platform; mu->frame_arena.cap = (mu->perm_arena.cap - mu->perm_arena.len) / 2; mu->frame_arena.memory = (char *)MU_PushSize(&mu->perm_arena, mu->frame_arena.cap); mu->time.delta = params.delta_time == 0.0 ? 0.0166666 : params.delta_time; mu->sound.callback = params.sound_callback; mu->params = params; if (mu->sound.callback) { MU_WIN32_InitWasapi(mu); MU_ASSERT(mu->sound.initialized); } typedef enum MU_PROCESS_DPI_AWARENESS { MU_PROCESS_DPI_UNAWARE = 0, MU_PROCESS_SYSTEM_DPI_AWARE = 1, MU_PROCESS_PER_MONITOR_DPI_AWARE = 2 } MU_PROCESS_DPI_AWARENESS; typedef unsigned MU_TimeBeginPeriod(unsigned); typedef HRESULT MU_SetProcessDpiAwareness(MU_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(MU_PROCESS_PER_MONITOR_DPI_AWARE); MU_ASSERT(SUCCEEDED(hr) && "Failed to set dpi awareness"); } } 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; } } } WNDCLASSW wc; { MU__ZeroMemory(&wc, sizeof(wc)); wc.lpfnWndProc = MU_WIN32_WindowProc; wc.hInstance = GetModuleHandleW(NULL); wc.lpszClassName = L"Multimedia_Start"; wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hIcon = NULL; // LoadIcon(wc.hInstance, IDI_APPLICATION); wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; MU_ASSERT_CODE(ATOM result =) RegisterClassW(&wc); MU_ASSERT(result != 0); w32->wc = wc; } mu->primary_monitor_size.x = GetSystemMetrics(SM_CXSCREEN); mu->primary_monitor_size.y = GetSystemMetrics(SM_CYSCREEN); w32->cursor_hand = LoadCursor(0, IDC_SIZEALL); w32->cursor_arrow = LoadCursor(0, IDC_ARROW); mu->time.app_start = MU_GetTime(); mu->first_frame = true; } MU_FN void MU_UpdateWindowState(MU_Window *window) { MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; UINT dpi = GetDpiForWindow((HWND)window->handle); MU_ASSERT(dpi != 0 && "Failed to get dpi for window"); window->dpi_scale = (float)dpi / 96.f; MU_Int2 size; MU_WIN32_GetWindowPos((HWND)window->handle, &window->pos.x, &window->pos.y); MU_Win32_GetWindowSize((HWND)window->handle, &size.x, &size.y); if (window->canvas_enabled == false || window->size.x != size.x || window->size.y != size.y) { MU_WIN32_DestroyCanvas(window); } window->size = size; window->sizef.x = (float)window->size.x; window->sizef.y = (float)window->size.y; window->posf.x = (float)window->pos.x; window->posf.y = (float)window->pos.y; if (window->canvas_enabled && window->canvas == 0) { MU_WIN32_CreateCanvas(window); } } MU_API MU_Window *MU_AddWindow(MU_Context *mu, MU_Window_Params params) { MU_Window *window = MU_PUSH_STRUCT(&mu->perm_arena, MU_Window); MU_InitWindow(mu, window, params); return window; } MU_API void MU_InitWindow(MU_Context *mu, MU_Window *window, MU_Window_Params params) { window->platform = MU_PUSH_STRUCT(&mu->perm_arena, MU_Win32_Window); MU_Win32 *w32 = (MU_Win32 *)mu->platform; MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; if (params.pos.x == 0) params.pos.x = (int)((double)mu->primary_monitor_size.x * 0.1); if (params.pos.y == 0) params.pos.y = (int)((double)mu->primary_monitor_size.y * 0.1); if (params.size.x == 0) params.size.x = (int)((double)mu->primary_monitor_size.x * 0.8); if (params.size.y == 0) params.size.y = (int)((double)mu->primary_monitor_size.y * 0.8); window->canvas_enabled = params.enable_canvas; w32_window->style = WS_OVERLAPPEDWINDOW; if (!params.resizable) { w32_window->style &= ~WS_THICKFRAME & ~WS_MAXIMIZEBOX; } if (params.borderless) { w32_window->style = WS_POPUP | WS_VISIBLE | WS_SYSMENU; } RECT window_rect; window_rect.left = (LONG)params.pos.x; window_rect.top = (LONG)params.pos.y; window_rect.right = (LONG)params.size.x + window_rect.left; window_rect.bottom = (LONG)params.size.y + window_rect.top; AdjustWindowRectEx(&window_rect, w32_window->style, false, 0); HWND handle = CreateWindowW(w32->wc.lpszClassName, L"Zzz... Window, hello!", w32_window->style, window_rect.left, window_rect.top, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, NULL, NULL, w32->wc.hInstance, NULL); MU_ASSERT(handle); window->handle = handle; w32_window->handle_dc = GetDC(handle); MU_ASSERT(w32_window->handle_dc); DragAcceptFiles(handle, TRUE); MU_WIN32_TryToInitGLContextForWindow(mu, w32_window); ShowWindow(handle, SW_SHOW); MU_UpdateWindowState(window); MU_STACK_ADD(mu->all_windows, window); MU_WIN32_UpdateFocusedWindow(mu); } MU_API MU_Context *MU_Start(MU_Params params) { // Bootstrap the context from user memory // If the user didnt provide memory, allocate it ourselves if (!params.memory) { HANDLE process_heap = GetProcessHeap(); params.cap = MU_DEFAULT_MEMORY_SIZE; params.memory = HeapAlloc(process_heap, 0, params.cap); MU_ASSERT(params.memory); } MU_Context *mu = (MU_Context *)params.memory; MU_Init(mu, params, sizeof(MU_Context)); mu->window = MU_AddWindow(mu, params.window); return mu; } MU_API bool MU_Update(MU_Context *mu) { MU_Win32 *w32 = (MU_Win32 *)mu->platform; // Since this is meant to be called in while loop // first MU_Update happens before first frame // therfore start of second MU_Update is end of first frame mu->_MU_Update_count += 1; if (mu->_MU_Update_count == 2) mu->first_frame = false; mu->frame_arena.len = 0; MU_WIN32_UpdateFocusedWindow(mu); for (MU_Window *it = mu->all_windows; it; it = it->next) { if (it->should_render == true && mu->first_frame == false && mu->opengl_initialized) { MU_Win32_Window *w32_window = (MU_Win32_Window *)it->platform; MU_ASSERT_CODE(BOOL result =) SwapBuffers(w32_window->handle_dc); MU_ASSERT(result); } it->should_render = true; it->first_dropped_file = 0; MU_WIN32_DrawCanvas(it); it->processed_events_this_frame = 0; it->user_text8_count = 0; it->user_text32_count = 0; it->mouse.delta_wheel = 0.0; it->mouse.left.press = 0; it->mouse.right.press = 0; it->mouse.middle.press = 0; it->mouse.left.unpress = 0; it->mouse.right.unpress = 0; it->mouse.middle.unpress = 0; for (int i = 0; i < MU_KEY_COUNT; i += 1) { it->key[i].press = 0; it->key[i].unpress = 0; it->key[i].raw_press = 0; } } MSG msg; MU_WIN32_ContextPointerForEventHandling = mu; while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } for (MU_Window *it = mu->all_windows; it; it = it->next) { MU_UpdateWindowState(it); } mu->window->user_text8[mu->window->user_text8_count] = 0; mu->window->user_text32[mu->window->user_text32_count] = 0; MU_Win32_Window *w32_window = (MU_Win32_Window *)mu->window->platform; HWND focused_window = GetFocus(); mu->window->is_focused = focused_window == (HWND)mu->window->handle; // We only need to update the mouse position of a currently focused window? { MU_Int2 mouse_pos = MU_WIN32_GetMousePositionInverted((HWND)mu->window->handle, mu->window->size.y); if (mu->window->is_focused) { if (mu->first_frame == false) { mu->window->mouse.delta_pos.x = mouse_pos.x - mu->window->mouse.pos.x; mu->window->mouse.delta_pos.y = mouse_pos.y - mu->window->mouse.pos.y; mu->window->mouse.delta_pos_normalized.x = (float)mu->window->mouse.delta_pos.x / (float)mu->window->size.x; mu->window->mouse.delta_pos_normalized.y = (float)mu->window->mouse.delta_pos.y / (float)mu->window->size.y; } if (mu->window->is_fps_mode) { SetCursorPos(mu->window->size.x / 2, mu->window->size.y / 2); mouse_pos = MU_WIN32_GetMousePositionInverted((HWND)mu->window->handle, mu->window->size.y); } } mu->window->mouse.pos = mouse_pos; mu->window->mouse.posf.x = (float)mouse_pos.x; mu->window->mouse.posf.y = (float)mouse_pos.y; } // Timming if (mu->first_frame == false) { mu->time.update = MU_GetTime() - mu->time.frame_start; if (mu->time.update < mu->time.delta) { mu->consecutive_missed_frames = 0; // Try to use the Sleep, if we dont have good scheduler priority // then we can miss framerate so need to busy loop instead if (w32->good_scheduling) { double time_to_sleep = mu->time.delta - mu->time.update; double time_to_sleep_in_ms = time_to_sleep * 1000.0 - 1; if (time_to_sleep > 0.0) { DWORD time_to_sleep_uint = (DWORD)time_to_sleep_in_ms; if (time_to_sleep_uint) { Sleep(time_to_sleep_uint); } } } // Busy loop if we dont have good scheduling // or we woke up early double update_time = MU_GetTime() - mu->time.frame_start; while (update_time < mu->time.delta) { update_time = MU_GetTime() - mu->time.frame_start; } } else { mu->consecutive_missed_frames += 1; mu->total_missed_frames += 1; } mu->frame += 1; mu->time.update_total = MU_GetTime() - mu->time.frame_start; mu->time.total += mu->time.delta; } mu->time.frame_start = MU_GetTime(); mu->time.deltaf = (float)mu->time.delta; mu->time.totalf = (float)mu->time.total; return !mu->quit; } // // Opengl context setup // // @! Cleanup OpenGL - Should the user be cappable of detecting that opengl couldnt load? // Should the layer automatically downscale? // Should the layer inform and allow for a response? /* MU_Context *mu = MU_Start((MU_Params){ .enable_opengl = true, }); if (mu->opengl_initialized == false) { mu_opengl_try_initializng_context_for_window(mu->window, 3, 3); } if (mu->opengl_initialized == false) { // directx } */ // 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); HGLRC(*mu_wglCreateContext) (HDC unnamedParam1); BOOL(*mu_wglMakeCurrent) (HDC unnamedParam1, HGLRC unnamedParam2); BOOL(*mu_wglDeleteContext) (HGLRC unnamedParam1); 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); MU_wglChoosePixelFormatARB *wglChoosePixelFormatARB; MU_wglCreateContextAttribsARB *wglCreateContextAttribsARB; MU_wglSwapIntervalEXT *wglSwapIntervalEXT; #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 // // Below loading part largely taken from github gist of Martins Mozeiko // // compares src string with dstlen characters from dst, returns 1 if they are equal, 0 if not MU_FN int MU__AreStringsEqual(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); } MU_FN void *MU_Win32_GLGetWindowProcAddressForGlad(const char *proc) { MU_Win32 *w32 = (MU_Win32 *)MU_WIN32_ContextPointerForEventHandling->platform; void *func; func = w32->wgl_get_proc_address(proc); if (!func) { func = GetProcAddress(w32->opengl32, proc); } return func; } MU_FN void MU_WIN32_GetWGLFunctions(MU_Context *mu) { MU_Win32 *w32 = (MU_Win32 *)mu->platform; HMODULE opengl32 = LoadLibraryA("opengl32"); MU_ASSERT(opengl32); if (opengl32) { w32->opengl32 = opengl32; w32->wgl_get_proc_address = (MU_glGetProcAddress *)GetProcAddress(opengl32, "wglGetProcAddress"); mu->gl_get_proc_address = MU_Win32_GLGetWindowProcAddressForGlad; 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 || mu->gl_get_proc_address == NULL || mu_wglMakeCurrent == NULL || mu_wglDeleteContext == NULL) { MU_ASSERT(!"Failed to load Opengl wgl functions from opengl32.lib"); return; } // 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); MU_ASSERT(dummy && "Failed to create dummy window"); HDC dc = GetDC(dummy); MU_ASSERT(dc && "Failed to get device context for dummy window"); PIXELFORMATDESCRIPTOR desc; MU__ZeroMemory(&desc, sizeof(desc)); { 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) { MU_ASSERT(!"Cannot choose OpenGL pixel format for dummy window!"); } int ok = DescribePixelFormat(dc, format, sizeof(desc), &desc); MU_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)) { MU_ASSERT(!"Cannot set OpenGL pixel format for dummy window!"); } HGLRC rc = mu_wglCreateContext(dc); MU_ASSERT(rc && "Failed to create OpenGL context for dummy window"); ok = mu_wglMakeCurrent(dc, rc); MU_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 *)mu->gl_get_proc_address("wglGetExtensionsStringARB"); if (!wglGetExtensionsStringARB) { MU_ASSERT(!"OpenGL does not support WGL_ARB_extensions_string extension!"); } const char *ext = wglGetExtensionsStringARB(dc); MU_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 (MU__AreStringsEqual("WGL_ARB_pixel_format", start, length)) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt wglChoosePixelFormatARB = (MU_wglChoosePixelFormatARB *)mu->gl_get_proc_address("wglChoosePixelFormatARB"); } else if (MU__AreStringsEqual("WGL_ARB_create_context", start, length)) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt wglCreateContextAttribsARB = (MU_wglCreateContextAttribsARB *)mu->gl_get_proc_address("wglCreateContextAttribsARB"); } else if (MU__AreStringsEqual("WGL_EXT_swap_control", start, length)) { // https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt wglSwapIntervalEXT = (MU_wglSwapIntervalEXT *)mu->gl_get_proc_address("wglSwapIntervalEXT"); } if (*ext == 0) { break; } ext++; start = ext; } if (!wglChoosePixelFormatARB || !wglCreateContextAttribsARB || !wglSwapIntervalEXT) { MU_ASSERT(!"OpenGL does not support required WGL extensions for modern context!"); } mu_wglMakeCurrent(NULL, NULL); mu_wglDeleteContext(rc); ReleaseDC(dummy, dc); DestroyWindow(dummy); mu->opengl_initialized = true; } MU_FN void MU_WIN32_TryToInitGLContextForWindow(MU_Context *mu, MU_Win32_Window *w32_window) { if (mu->opengl_initialized == false && mu->params.enable_opengl) { MU_WIN32_GetWGLFunctions(mu); if (mu->opengl_initialized) { mu->opengl_major = mu->params.opengl_major ? mu->params.opengl_major : 4; mu->opengl_minor = mu->params.opengl_minor ? mu->params.opengl_minor : 5; } } if (mu->opengl_initialized) { // 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 MU_GL_ENABLE_MULTISAMPLING WGL_SAMPLE_BUFFERS_ARB, 1, WGL_SAMPLES_ARB, 4, // 4x MSAA #endif 0, }; int format; UINT formats; if (!wglChoosePixelFormatARB(w32_window->handle_dc, attrib, 0, 1, &format, &formats) || formats == 0) { MU_ASSERT(!"OpenGL does not support required pixel format!"); } PIXELFORMATDESCRIPTOR desc; MU__ZeroMemory(&desc, sizeof(desc)); desc.nSize = sizeof(desc); int ok = DescribePixelFormat(w32_window->handle_dc, format, sizeof(desc), &desc); MU_ASSERT(ok && "Failed to describe OpenGL pixel format"); if (!SetPixelFormat(w32_window->handle_dc, format, &desc)) { MU_ASSERT(!"Cannot set OpenGL selected pixel format!"); } // create modern OpenGL context { int attrib[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, mu->opengl_major, WGL_CONTEXT_MINOR_VERSION_ARB, mu->opengl_minor, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, #if MU_GL_BUILD_DEBUG WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, #endif 0, }; HGLRC rc = wglCreateContextAttribsARB(w32_window->handle_dc, 0, attrib); if (!rc) { MU_ASSERT(!"Cannot create modern OpenGL context! OpenGL version 4.5 not supported?"); } BOOL ok = mu_wglMakeCurrent(w32_window->handle_dc, rc); MU_ASSERT(ok && "Failed to make current OpenGL context"); } } } // // Sound using WASAPI // @! Sound: Comeback to it later! I dont really know what I should expect from a sound system // What actually in reality errors out in WASAPI, what is important when working with sound. // As such I'm not really currently equiped to make something good / reliable. // Probably would be nice to work with it a bit more. // // Sound params should probably be configurable // but I dont really understand what I should want to expect // from this sort of system // // Not sure if I should in the future implement some different non threaded api. // // // Below GUID stuff taken from libsoundio // reference: https://github.com/andrewrk/libsoundio/blob/master/src/wasapi.c // // And some GUID are never implemented (Ignoring the INITGUID define) MU_PRIVATE_VAR const CLSID MU_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} }; MU_PRIVATE_VAR const IID MU_IID_IMMDeviceEnumerator = { // MIDL_INTERFACE("A95664D2-9614-4F35-A746-DE8DB63617E6") 0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} }; MU_PRIVATE_VAR const IID MU_IID_IMMNotificationClient = { // MIDL_INTERFACE("7991EEC9-7E89-4D85-8390-6C703CEC60C0") 0x7991eec9, 0x7e89, 0x4d85, {0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0} }; MU_PRIVATE_VAR const IID MU_IID_IAudioClient = { // MIDL_INTERFACE("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") 0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} }; MU_PRIVATE_VAR const IID MU_IID_IAudioRenderClient = { // MIDL_INTERFACE("F294ACFC-3146-4483-A7BF-ADDCA7C260E2") 0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} }; MU_PRIVATE_VAR const IID MU_IID_IAudioSessionControl = { // MIDL_INTERFACE("F4B1A599-7266-4319-A8CA-E70ACB11E8CD") 0xf4b1a599, 0x7266, 0x4319, {0xa8, 0xca, 0xe7, 0x0a, 0xcb, 0x11, 0xe8, 0xcd} }; MU_PRIVATE_VAR const IID MU_IID_IAudioSessionEvents = { // MIDL_INTERFACE("24918ACC-64B3-37C1-8CA9-74A66E9957A8") 0x24918acc, 0x64b3, 0x37c1, {0x8c, 0xa9, 0x74, 0xa6, 0x6e, 0x99, 0x57, 0xa8} }; MU_PRIVATE_VAR const IID MU_IID_IMMEndpoint = { // MIDL_INTERFACE("1BE09788-6894-4089-8586-9A2A6C265AC5") 0x1be09788, 0x6894, 0x4089, {0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5} }; MU_PRIVATE_VAR const IID MU_IID_IAudioClockAdjustment = { // MIDL_INTERFACE("f6e4c0a0-46d9-4fb8-be21-57a3ef2b626c") 0xf6e4c0a0, 0x46d9, 0x4fb8, {0xbe, 0x21, 0x57, 0xa3, 0xef, 0x2b, 0x62, 0x6c} }; MU_PRIVATE_VAR const IID MU_IID_IAudioCaptureClient = { // MIDL_INTERFACE("C8ADBD64-E71E-48a0-A4DE-185C395CD317") 0xc8adbd64, 0xe71e, 0x48a0, {0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17} }; MU_PRIVATE_VAR const IID MU_IID_ISimpleAudioVolume = { // MIDL_INTERFACE("87ce5498-68d6-44e5-9215-6da47ef883d8") 0x87ce5498, 0x68d6, 0x44e5, {0x92, 0x15, 0x6d, 0xa4, 0x7e, 0xf8, 0x83, 0xd8} }; #ifdef __cplusplus // In C++ mode, IsEqualGUID() takes its arguments by reference #define IS_EQUAL_GUID(a, b) IsEqualGUID(*(a), *(b)) #define IS_EQUAL_IID(a, b) IsEqualIID((a), *(b)) // And some constants are passed by reference #define MU_IID_IAUDIOCLIENT (MU_IID_IAudioClient) #define MU_IID_IMMENDPOINT (MU_IID_IMMEndpoint) #define MU_IID_IAUDIOCLOCKADJUSTMENT (MU_IID_IAudioClockAdjustment) #define MU_IID_IAUDIOSESSIONCONTROL (MU_IID_IAudioSessionControl) #define MU_IID_IAUDIORENDERCLIENT (MU_IID_IAudioRenderClient) #define MU_IID_IMMDEVICEENUMERATOR (MU_IID_IMMDeviceEnumerator) #define MU_IID_IAUDIOCAPTURECLIENT (MU_IID_IAudioCaptureClient) #define MU_IID_ISIMPLEAUDIOVOLUME (MU_IID_ISimpleAudioVolume) #define MU_CLSID_MMDEVICEENUMERATOR (MU_CLSID_MMDeviceEnumerator) #define MU_PKEY_DEVICE_FRIENDLYNAME (PKEY_Device_FriendlyName) #define MU_PKEY_AUDIOENGINE_DEVICEFORMAT (PKEY_AudioEngine_DeviceFormat) #else #define IS_EQUAL_GUID(a, b) IsEqualGUID((a), (b)) #define IS_EQUAL_IID(a, b) IsEqualIID((a), (b)) #define MU_IID_IAUDIOCLIENT (&MU_IID_IAudioClient) #define MU_IID_IMMENDPOINT (&MU_IID_IMMEndpoint) #define MU_PKEY_DEVICE_FRIENDLYNAME (&PKEY_Device_FriendlyName) #define MU_PKEY_AUDIOENGINE_DEVICEFORMAT (&PKEY_AudioEngine_DeviceFormat) #define MU_CLSID_MMDEVICEENUMERATOR (&MU_CLSID_MMDeviceEnumerator) #define MU_IID_IAUDIOCLOCKADJUSTMENT (&MU_IID_IAudioClockAdjustment) #define MU_IID_IAUDIOSESSIONCONTROL (&MU_IID_IAudioSessionControl) #define MU_IID_IAUDIORENDERCLIENT (&MU_IID_IAudioRenderClient) #define MU_IID_IMMDEVICEENUMERATOR (&MU_IID_IMMDeviceEnumerator) #define MU_IID_IAUDIOCAPTURECLIENT (&MU_IID_IAudioCaptureClient) #define MU_IID_ISIMPLEAUDIOVOLUME (&MU_IID_ISimpleAudioVolume) #endif // Number of REFERENCE_TIME units per second // One unit is equal to 100 nano seconds #define MU_REF_TIMES_PER_SECOND 10000000 #define MU_REF_TIMES_PER_MSECOND 10000 // Empty functions(stubs) which are used when library fails to load static HRESULT CoCreateInstanceStub(REFCLSID rclsid, LPUNKNOWN *pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv) { (void)(rclsid); (void)(pUnkOuter); (void)(dwClsContext); (void)(riid); (void)(ppv); return S_FALSE; } static HRESULT CoInitializeExStub(LPVOID pvReserved, DWORD dwCoInit) { (void)(pvReserved); (void)(dwCoInit); return S_FALSE; } MU_FN void MU_WIN32_DeinitSound(MU_Context *mu) { MU_Win32 *w32 = (MU_Win32 *)mu->platform; if (w32->audio_client) w32->audio_client->lpVtbl->Stop(w32->audio_client); if (w32->audio_client) w32->audio_client->lpVtbl->Release(w32->audio_client); if (w32->device_enum) w32->device_enum->lpVtbl->Release(w32->device_enum); if (w32->device) w32->device->lpVtbl->Release(w32->device); if (w32->audio_render_client) w32->audio_render_client->lpVtbl->Release(w32->audio_render_client); mu->sound.initialized = false; } // Load COM Library functions dynamically, // this way sound is not necessary to run the game MU_FN void MU_WIN32_LoadCOM(MU_Context *mu) { MU_Win32 *w32 = (MU_Win32 *)mu->platform; HMODULE ole32_lib = LoadLibraryA("ole32.dll"); if (ole32_lib) { w32->CoCreateInstanceFunctionPointer = (CoCreateInstanceFunction *)GetProcAddress(ole32_lib, "CoCreateInstance"); w32->CoInitializeExFunctionPointer = (CoInitializeExFunction *)GetProcAddress(ole32_lib, "CoInitializeEx"); mu->sound.initialized = true; } if (ole32_lib == 0 || w32->CoCreateInstanceFunctionPointer == 0 || w32->CoInitializeExFunctionPointer == 0) { w32->CoCreateInstanceFunctionPointer = CoCreateInstanceStub; w32->CoInitializeExFunctionPointer = CoInitializeExStub; mu->sound.initialized = false; } } MU_FN DWORD MU_WIN32_SoundThread(void *parameter) { MU_Context *mu = (MU_Context *)parameter; MU_Win32 *w32 = (MU_Win32 *)mu->platform; HANDLE thread_handle = GetCurrentThread(); SetThreadPriority(thread_handle, THREAD_PRIORITY_HIGHEST); HANDLE buffer_ready_event = CreateEvent(0, 0, 0, 0); if (!buffer_ready_event) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } if (FAILED(IAudioClient_SetEventHandle(w32->audio_client, buffer_ready_event))) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } if (FAILED(IAudioClient_Start(w32->audio_client))) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } for (;;) { if (WaitForSingleObject(buffer_ready_event, INFINITE) != WAIT_OBJECT_0) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } uint32_t padding_frame_count; if (FAILED(IAudioClient_GetCurrentPadding(w32->audio_client, &padding_frame_count))) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } uint32_t *samples; uint32_t fill_frame_count = w32->buffer_frame_count - padding_frame_count; if (FAILED(IAudioRenderClient_GetBuffer(w32->audio_render_client, fill_frame_count, (BYTE **)&samples))) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } // Call user callback uint32_t sample_count_to_fill = fill_frame_count * mu->sound.number_of_channels; mu->sound.callback((MU_Context *)mu, (uint16_t *)samples, sample_count_to_fill); if (FAILED(IAudioRenderClient_ReleaseBuffer(w32->audio_render_client, fill_frame_count, 0))) { MU_ASSERT(!"Sound thread failed"); goto error_cleanup; } } return 0; error_cleanup: MU_WIN32_DeinitSound(mu); return -1; } MU_FN void MU_WIN32_InitWasapi(MU_Context *mu) { REFERENCE_TIME requested_buffer_duration = MU_REF_TIMES_PER_MSECOND * 40; MU_Win32 *w32 = (MU_Win32 *)mu->platform; MU_WIN32_LoadCOM(mu); MU_ASSERT(mu->sound.initialized); if (mu->sound.initialized == false) { return; } mu->sound.bytes_per_sample = 2; mu->sound.number_of_channels = 2; mu->sound.samples_per_second = 44100; HANDLE thread_handle; HRESULT hr = w32->CoInitializeExFunctionPointer(0, COINITBASE_MULTITHREADED); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } hr = w32->CoCreateInstanceFunctionPointer(MU_CLSID_MMDEVICEENUMERATOR, NULL, CLSCTX_ALL, MU_IID_IMMDEVICEENUMERATOR, (void **)&w32->device_enum); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(w32->device_enum, eRender, eMultimedia, &w32->device); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } hr = IMMDevice_Activate(w32->device, MU_IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void **)&w32->audio_client); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } WAVEFORMATEX fmt; { MU__ZeroMemory(&fmt, sizeof(fmt)); fmt.wFormatTag = WAVE_FORMAT_PCM; fmt.nChannels = mu->sound.number_of_channels; fmt.nSamplesPerSec = mu->sound.samples_per_second; fmt.wBitsPerSample = mu->sound.bytes_per_sample * 8; fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; } hr = IAudioClient_Initialize( w32->audio_client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_RATEADJUST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, requested_buffer_duration, 0, &fmt, 0); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } hr = IAudioClient_GetService(w32->audio_client, MU_IID_IAUDIORENDERCLIENT, (void **)&w32->audio_render_client); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } hr = IAudioClient_GetBufferSize(w32->audio_client, &w32->buffer_frame_count); if (FAILED(hr)) { MU_ASSERT(!"Failed to initialize sound"); goto failure_path; } thread_handle = CreateThread(0, 0, MU_WIN32_SoundThread, mu, 0, 0); if (thread_handle == INVALID_HANDLE_VALUE) { MU_ASSERT(!"Failed to create a sound thread"); goto failure_path; } return; failure_path: MU_WIN32_DeinitSound(mu); } #endif // _WIN32 #endif // MU_IMPLEMENTATION