// TODO(allen): Changed durring this jam // WM_SYSKEYDOWN bell sounds removed #include #include #include #include #include #include #include #undef DeleteFile // NOTE(rjf): OpenGL #include #include "ext/wglext.h" #include "ext/glext.h" // NOTE(rjf): CRT #include // NOTE(rjf): Headers #include "program_options.h" #include "language_layer.h" #include "app_memory.h" #include "os.h" #include "win32_timer.h" #include "language_layer.c" #include "app_memory.c" #include "os.c" // NOTE(rjf): Globals global char global_executable_path[256]; global char global_executable_directory[256]; global char global_working_directory[256]; global char global_app_dll_path[256]; global char global_temp_app_dll_path[256]; global OS_State global_os; global HDC global_device_context; global HINSTANCE global_instance_handle; global W32_Timer global_win32_timer = {0}; #define W32_MAX_GAMEPADS 16 typedef struct W32_GamepadInput W32_GamepadInput; struct W32_GamepadInput { b32 connected; v2 joystick_1; v2 joystick_2; f32 trigger_left; f32 trigger_right; i32 button_states[GamepadButton_Max]; }; W32_GamepadInput global_gamepads[W32_MAX_GAMEPADS]; // NOTE(rjf): Implementations #include "win32_utilities.c" #include "win32_timer.c" #include "win32_file_io.c" #include "win32_app_code.c" #include "win32_xinput.c" #include "win32_wasapi.c" #include "win32_opengl.c" #include "win32_thread.c" //~ typedef enum W32_CursorStyle { W32_CursorStyle_Normal, W32_CursorStyle_HorizontalResize, W32_CursorStyle_VerticalResize, W32_CursorStyle_IBar, } W32_CursorStyle; global W32_CursorStyle global_cursor_style = 0; internal v2 W32_GetMousePosition(HWND window) { v2 result = {0}; POINT mouse; GetCursorPos(&mouse); ScreenToClient(window, &mouse); result.x = (f32)(mouse.x); result.y = (f32)(mouse.y); return result; } internal LRESULT W32_WindowProc(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param) { LRESULT result = 0; local_persist b32 mouse_hover_active_because_windows_makes_me_cry = 0; KeyModifiers modifiers = 0; if(GetKeyState(VK_CONTROL) & 0x8000) { modifiers |= KeyModifier_Ctrl; } if(GetKeyState(VK_SHIFT) & 0x8000) { modifiers |= KeyModifier_Shift; } if(GetKeyState(VK_MENU) & 0x8000) { modifiers |= KeyModifier_Alt; } if(message == WM_CLOSE || message == WM_DESTROY || message == WM_QUIT) { global_os.quit = 1; result = 0; } else if(message == WM_LBUTTONDOWN) { OS_PushEvent(OS_MousePressEvent(MouseButton_Left, global_os.mouse_position)); } else if(message == WM_LBUTTONUP) { OS_PushEvent(OS_MouseReleaseEvent(MouseButton_Left, global_os.mouse_position)); } else if(message == WM_RBUTTONDOWN) { OS_PushEvent(OS_MousePressEvent(MouseButton_Right, global_os.mouse_position)); } else if(message == WM_RBUTTONUP) { OS_PushEvent(OS_MouseReleaseEvent(MouseButton_Right, global_os.mouse_position)); } else if(message == WM_MOUSEMOVE) { i16 x_position = LOWORD(l_param); i16 y_position = HIWORD(l_param); v2 last_mouse = global_os.mouse_position; global_os.mouse_position = W32_GetMousePosition(window_handle); OS_PushEvent(OS_MouseMoveEvent(global_os.mouse_position, v2(global_os.mouse_position.x - last_mouse.x, global_os.mouse_position.y - last_mouse.y))); if(mouse_hover_active_because_windows_makes_me_cry == 0) { mouse_hover_active_because_windows_makes_me_cry = 1; TRACKMOUSEEVENT track_mouse_event = {0}; { track_mouse_event.cbSize = sizeof(track_mouse_event); track_mouse_event.dwFlags = TME_LEAVE; track_mouse_event.hwndTrack = window_handle; track_mouse_event.dwHoverTime = HOVER_DEFAULT; } TrackMouseEvent(&track_mouse_event); } } else if(message == WM_MOUSELEAVE) { mouse_hover_active_because_windows_makes_me_cry = 0; } else if(message == WM_MOUSEWHEEL) { i16 wheel_delta = HIWORD(w_param); OS_PushEvent(OS_MouseScrollEvent(v2(0, (f32)wheel_delta), modifiers)); } else if(message == WM_MOUSEHWHEEL) { i16 wheel_delta = HIWORD(w_param); OS_PushEvent(OS_MouseScrollEvent(v2((f32)wheel_delta, 0), modifiers)); } else if(message == WM_SETCURSOR) { if(global_os.mouse_position.x >= 1 && global_os.mouse_position.x <= global_os.window_size.x-1 && global_os.mouse_position.y >= 1 && global_os.mouse_position.y <= global_os.window_size.y-1 && mouse_hover_active_because_windows_makes_me_cry) { switch(global_cursor_style) { case W32_CursorStyle_HorizontalResize: { SetCursor(LoadCursorA(0, IDC_SIZEWE)); break; } case W32_CursorStyle_VerticalResize: { SetCursor(LoadCursorA(0, IDC_SIZENS)); break; } case W32_CursorStyle_IBar: { SetCursor(LoadCursorA(0, IDC_IBEAM)); break; } case W32_CursorStyle_Normal: { SetCursor(LoadCursorA(0, IDC_ARROW)); break; } default: break; } } else { result = DefWindowProc(window_handle, message, w_param, l_param); } } else if(message == WM_SYSKEYDOWN || message == WM_SYSKEYUP || message == WM_KEYDOWN || message == WM_KEYUP) { u64 vkey_code = w_param; i8 was_down = !!(l_param & (1 << 30)); i8 is_down = !(l_param & (1 << 31)); u64 key_input = 0; if((vkey_code >= 'A' && vkey_code <= 'Z') || (vkey_code >= '0' && vkey_code <= '9')) { // NOTE(rjf): Letter/number buttons key_input = (vkey_code >= 'A' && vkey_code <= 'Z') ? Key_A + (vkey_code-'A') : Key_0 + (vkey_code-'0'); } else { if(vkey_code == VK_ESCAPE) { key_input = Key_Esc; } else if(vkey_code >= VK_F1 && vkey_code <= VK_F12) { key_input = Key_F1 + vkey_code - VK_F1; } else if(vkey_code == VK_OEM_3) { key_input = Key_GraveAccent; } else if(vkey_code == VK_OEM_MINUS) { key_input = Key_Minus; } else if(vkey_code == VK_OEM_PLUS) { key_input = Key_Equal; } else if(vkey_code == VK_BACK) { key_input = Key_Backspace; } else if(vkey_code == VK_TAB) { key_input = Key_Tab; } else if(vkey_code == VK_SPACE) { key_input = Key_Space; } else if(vkey_code == VK_RETURN) { key_input = Key_Enter; } else if(vkey_code == VK_CONTROL) { key_input = Key_Ctrl; modifiers &= ~KeyModifier_Ctrl; } else if(vkey_code == VK_SHIFT) { key_input = Key_Shift; modifiers &= ~KeyModifier_Shift; } else if(vkey_code == VK_MENU) { key_input = Key_Alt; modifiers &= ~KeyModifier_Alt; } else if(vkey_code == VK_UP) { key_input = Key_Up; } else if(vkey_code == VK_LEFT) { key_input = Key_Left; } else if(vkey_code == VK_DOWN) { key_input = Key_Down; } else if(vkey_code == VK_RIGHT) { key_input = Key_Right; } else if(vkey_code == VK_DELETE) { key_input = Key_Delete; } else if(vkey_code == VK_PRIOR) { key_input = Key_PageUp; } else if(vkey_code == VK_NEXT) { key_input = Key_PageDown; } else if(vkey_code == VK_HOME) { key_input = Key_Home; } else if(vkey_code == VK_END) { key_input = Key_End; } else if(vkey_code == VK_OEM_2) { key_input = Key_ForwardSlash; } else if(vkey_code == VK_OEM_PERIOD) { key_input = Key_Period; } else if(vkey_code == VK_OEM_COMMA) { key_input = Key_Comma; } else if(vkey_code == VK_OEM_7) { key_input = Key_Quote; } else if(vkey_code == VK_OEM_4) { key_input = Key_LeftBracket; } else if(vkey_code == VK_OEM_6) { key_input = Key_RightBracket; } } if(is_down) { OS_PushEvent(OS_KeyPressEvent(key_input, modifiers)); } else { OS_PushEvent(OS_KeyReleaseEvent(key_input, modifiers)); } DefWindowProc(window_handle, message, w_param, l_param); } else if (message == WM_SYSCHAR){ // NOTE(allen): Do nothing - just don't fall through to DefWindowProc which will // cause bell sounds for alt modified combos. } else if(message == WM_CHAR) { u64 char_input = w_param; if(char_input >= 32 && char_input != VK_RETURN && char_input != VK_ESCAPE && char_input != 127) { OS_PushEvent(OS_CharacterInputEvent(char_input)); } } else { result = DefWindowProc(window_handle, message, w_param, l_param); } return result; } internal f32 W32_GetTime(void) { W32_Timer *timer = &global_win32_timer; LARGE_INTEGER current_time; QueryPerformanceCounter(¤t_time); return global_os.current_time + (f32)(current_time.QuadPart - timer->begin_frame.QuadPart) / (f32)timer->counts_per_second.QuadPart; } internal u64 W32_GetCycles(void) { u64 result = __rdtsc(); return result; } internal void W32_ResetCursor(void) { global_cursor_style = W32_CursorStyle_Normal; } internal void W32_SetCursorToHorizontalResize(void) { global_cursor_style = W32_CursorStyle_HorizontalResize; } internal void W32_SetCursorToVerticalResize(void) { global_cursor_style = W32_CursorStyle_VerticalResize; } int WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR lp_cmd_line, int n_show_cmd) { global_instance_handle = instance; W32_TimerInit(&global_win32_timer); W32_AppCode win32_game_code = {0}; W32_SoundOutput win32_sound_output = {0}; // NOTE(rjf): Calculate executable name and path to DLL { DWORD size_of_executable_path = GetModuleFileNameA(0, global_executable_path, sizeof(global_executable_path)); // NOTE(rjf): Calculate executable directory { MemoryCopy(global_executable_directory, global_executable_path, size_of_executable_path); char *one_past_last_slash = global_executable_directory; for(i32 i = 0; global_executable_directory[i]; ++i) { if(global_executable_directory[i] == '\\') { one_past_last_slash = global_executable_directory + i + 1; } } *one_past_last_slash = 0; } // NOTE(rjf): Create DLL filenames { wsprintf(global_app_dll_path, "%s%s.dll", global_executable_directory, PROGRAM_FILENAME); wsprintf(global_temp_app_dll_path, "%stemp_%s.dll", global_executable_directory, PROGRAM_FILENAME); } GetCurrentDirectory(sizeof(global_working_directory), global_working_directory); } WNDCLASS window_class = {0}; { window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.lpfnWndProc = W32_WindowProc; window_class.hInstance = instance; window_class.lpszClassName = "ApplicationWindowClass"; window_class.hCursor = LoadCursor(0, IDC_ARROW); } if(!RegisterClass(&window_class)) { // NOTE(rjf): ERROR: Window class registration failure W32_OutputError("Fatal Error", "Window class registration failure."); goto quit; } HWND window_handle = CreateWindow("ApplicationWindowClass", WINDOW_TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT, 0, 0, instance, 0); if(!window_handle) { // NOTE(rjf): ERROR: Window creation failure W32_OutputError("Fatal Error", "Window creation failure."); goto quit; } // NOTE(rjf): Load application code W32_AppCode win32_app_code = {0}; { if(!W32_AppCodeLoad(&win32_app_code)) { // NOTE(rjf): ERROR: Application code load failure W32_OutputError("Fatal Error", "Application code load failure."); goto quit; } } // NOTE(rjf): Sound initialization { win32_sound_output.channels = 2; win32_sound_output.samples_per_second = 48000; win32_sound_output.latency_frame_count = 48000; W32_LoadWASAPI(); W32_InitWASAPI(&win32_sound_output); } // NOTE(rjf): Find refresh rate f32 refresh_rate = 60.f; { DEVMODEA device_mode = {0}; if(EnumDisplaySettingsA(0, ENUM_CURRENT_SETTINGS, &device_mode)) { refresh_rate = (float)device_mode.dmDisplayFrequency; } } // NOTE(rjf): Initialize platform { os = &global_os; global_os.executable_folder_absolute_path = String8FromCString(global_executable_directory); global_os.executable_absolute_path = String8FromCString(global_executable_path); global_os.working_directory_path = String8FromCString(global_working_directory); global_os.quit = 0; global_os.vsync = 1; global_os.fullscreen = 0; global_os.window_size.x = DEFAULT_WINDOW_WIDTH; global_os.window_size.y = DEFAULT_WINDOW_HEIGHT; global_os.current_time = 0.f; global_os.target_frames_per_second = refresh_rate; global_os.sample_out = W32_HeapAlloc(win32_sound_output.samples_per_second * sizeof(f32) * 2); global_os.samples_per_second = win32_sound_output.samples_per_second; global_os.Reserve = W32_Reserve; global_os.Release = W32_Release; global_os.Commit = W32_Commit; global_os.Decommit = W32_Decommit; global_os.OutputError = W32_OutputError; global_os.SaveToFile = W32_SaveToFile; global_os.AppendToFile = W32_AppendToFile; global_os.LoadEntireFile = W32_LoadEntireFile; global_os.LoadEntireFileAndNullTerminate = W32_LoadEntireFileAndNullTerminate; global_os.DeleteFile = W32_DeleteFile; global_os.MakeDirectory = W32_MakeDirectory; global_os.DoesFileExist = W32_DoesFileExist; global_os.DoesDirectoryExist = W32_DoesDirectoryExist; global_os.CopyFile = W32_CopyFile; global_os.ListDirectory = W32_DirectoryListLoad; global_os.GetTime = W32_GetTime; global_os.GetCycles = W32_GetCycles; global_os.ResetCursor = W32_ResetCursor; global_os.SetCursorToHorizontalResize = W32_SetCursorToHorizontalResize; global_os.SetCursorToVerticalResize = W32_SetCursorToVerticalResize; global_os.LoadOpenGLProcedure = W32_LoadOpenGLProcedure; global_os.RefreshScreen = W32_OpenGLRefreshScreen; global_os.GetThreadContext = W32_GetThreadContext; global_os.DialogueSavePath = W32_DialogueSavePath; global_os.DialogueLoadPath = W32_DialogueLoadPath; global_os.permanent_arena = M_ArenaInitialize(); global_os.frame_arena = M_ArenaInitialize(); } // NOTE(allen): Thread Context OS_ThreadContext tctx_; OS_ArenaNode tctx_scratch_nodes[3]; W32_ThreadInit(&tctx_, tctx_scratch_nodes, 3); // NOTE(rjf): OpenGL initialization { global_device_context = GetDC(window_handle); if(!W32_InitOpenGL(&global_device_context, global_instance_handle)) { W32_OutputError("Fatal Error", "OpenGL initialization failure."); goto quit; } } W32_LoadXInput(); win32_app_code.PermanentLoad(&global_os); win32_app_code.HotLoad(&global_os); ShowWindow(window_handle, n_show_cmd); UpdateWindow(window_handle); while(!global_os.quit) { W32_TimerBeginFrame(&global_win32_timer); M_ArenaClear(&os->frame_arena); // NOTE(rjf): Update Windows events { os->event_count = 0; MSG message; if(global_os.wait_for_events_to_update && !global_os.pump_events) { WaitMessage(); } while(PeekMessage(&message, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&message); DispatchMessage(&message); } } // NOTE(rjf): Update window size { RECT client_rect; GetClientRect(window_handle, &client_rect); global_os.window_size.x = client_rect.right - client_rect.left; global_os.window_size.y = client_rect.bottom - client_rect.top; } // NOTE(rjf): Update input data (post-event) OS_BeginFrame(); { POINT mouse; GetCursorPos(&mouse); ScreenToClient(window_handle, &mouse); W32_UpdateXInput(); global_os.pump_events = 0; } // NOTE(rjf): Find how much sound to write and where if(win32_sound_output.initialized) { global_os.sample_count_to_output = 0; UINT32 sound_padding_size; if(SUCCEEDED(win32_sound_output.audio_client->lpVtbl->GetCurrentPadding(win32_sound_output.audio_client, &sound_padding_size))) { global_os.samples_per_second = win32_sound_output.samples_per_second; global_os.sample_count_to_output = (u32)(win32_sound_output.latency_frame_count - sound_padding_size); if(global_os.sample_count_to_output > win32_sound_output.latency_frame_count) { global_os.sample_count_to_output = win32_sound_output.latency_frame_count; } } for(u32 i = 0; i < win32_sound_output.buffer_frame_count; ++i) { global_os.sample_out[i] = 0; } } // NOTE(rjf): Call into the app layer to update { b32 last_fullscreen = global_os.fullscreen; win32_app_code.Update(); // NOTE(rjf): Update fullscreen if necessary if(last_fullscreen != global_os.fullscreen) { W32_ToggleFullscreen(window_handle); } // NOTE(rjf): Fill sound buffer with game sound if(win32_sound_output.initialized) { W32_FillSoundBuffer(global_os.sample_count_to_output, global_os.sample_out, &win32_sound_output); } } // NOTE(rjf): Post-update platform data update { OS_EndFrame(); } W32_AppCodeUpdate(&win32_app_code); W32_TimerEndFrame(&global_win32_timer, 1000.0 * (1.0 / (f64)global_os.target_frames_per_second)); } ShowWindow(window_handle, SW_HIDE); W32_AppCodeUnload(&win32_app_code); W32_CleanUpOpenGL(&global_device_context); quit:; return 0; }