2112 lines
76 KiB
C
2112 lines
76 KiB
C
#ifndef MU_HEADER
|
|
#define MU_HEADER
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#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 <windows.h>
|
|
#include <shellapi.h> // for handling dropped files
|
|
|
|
#define INITGUID
|
|
#define CINTERFACE
|
|
#define COBJMACROS
|
|
#define CONST_VTABLE
|
|
#include <objbase.h>
|
|
#include <audioclient.h>
|
|
#include <audiopolicy.h>
|
|
#include <mmdeviceapi.h>
|
|
|
|
//
|
|
// 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 <assert.h>
|
|
#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
|