#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include "windows.h" #include "timeapi.h" #include "vn_platform.h" #include "win32_main.h" #include "win32_opengl.cpp" global win32_state Global_Win32State; global WINDOWPLACEMENT Global_WindowPosition = {sizeof(Global_WindowPosition)};; static void Win32_PlatformError(char *Message, bool IsFatal) { MessageBoxA(0, Message, "vn - Platform Error", MB_OK|(IsFatal?MB_ICONSTOP:MB_ICONEXCLAMATION)); if(IsFatal) { ExitProcess((UINT)-1); } } static PLATFORM_SHOW_MESSAGE(Win32_ShowMessage) { DWORD Flags = MB_OK; switch(Type) { case Platform_Message_Info: { Flags |= MB_ICONINFORMATION; } break; case Platform_Message_Warning: { Flags |= MB_ICONWARNING; } break; case Platform_Message_Error: { Flags |= MB_ICONEXCLAMATION; } break; case Platform_Message_Fatal: { Flags |= MB_ICONSTOP; } break; InvalidDefaultCase; } // sixten(NOTE): Check for null-termination. Assert(Message.Data[Message.Count] == 0); MessageBoxA(0, (char *)Message.Data, "vn - A message from the developer", Flags); if(Type == Platform_Message_Fatal) { ExitProcess((UINT)-1); } } static PLATFORM_ALLOCATE_MEMORY(Win32_AllocateMemory) { win32_state *State = &Global_Win32State; umm TotalSize = Size + sizeof(win32_memory_block); win32_memory_block *Block = (win32_memory_block *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); Assert(Block); Block->Block.Base = (u8 *)Block + sizeof(win32_memory_block); Block->Block.Size = Size; win32_memory_block *Sentinel = &State->MemorySentinel; Block->Next = Sentinel; BeginTicketMutex(&State->MemoryMutex); Block->Prev = Sentinel->Prev; Block->Prev->Next = Block; Block->Next->Prev = Block; EndTicketMutex(&State->MemoryMutex); platform_memory_block *Result = &Block->Block; return(Result); } static PLATFORM_DEALLOCATE_MEMORY(Win32_DeallocateMemory) { win32_state *State = &Global_Win32State; win32_memory_block *Win32Block = (win32_memory_block *)Block; if(Block) { BeginTicketMutex(&State->MemoryMutex); Win32Block->Prev->Next = Win32Block->Next; Win32Block->Next->Prev = Win32Block->Prev; EndTicketMutex(&State->MemoryMutex); } BOOL Result = VirtualFree(Block, 0, MEM_RELEASE); return(Result); } static PLATFORM_OPEN_FILE(Win32_OpenFile) { DWORD DesiredAccess = 0; if(FileAccess & PlatformAccess_Read) { DesiredAccess |= GENERIC_READ; } if(FileAccess & PlatformAccess_Write) { DesiredAccess |= GENERIC_WRITE; } DWORD CreationAttributes = 0; if(FileAccess & PlatformAccess_Read) { CreationAttributes = OPEN_EXISTING; } if(FileAccess & PlatformAccess_Write) { CreationAttributes = CREATE_ALWAYS; } temporary_memory Scratch = GetScratch(0, 0); string FullPath = PushFormat(Scratch.Arena, "%S\\%S", Global_Win32State.ContentsPath, Path); HANDLE File = CreateFileA((char *)FullPath.Data, DesiredAccess, 0, 0, CreationAttributes, 0, 0); ReleaseScratch(Scratch); platform_file_handle Result = {}; Result.Platform = (u64)File; Result.IsValid = (File != INVALID_HANDLE_VALUE); return(Result); } static PLATFORM_CLOSE_FILE(Win32_CloseFile) { HANDLE File = (HANDLE)Handle.Platform; if(File != INVALID_HANDLE_VALUE) { CloseHandle(File); } } static PLATFORM_READ_FILE(Win32_ReadFile) { HANDLE File = (HANDLE)Handle.Platform; if(File != INVALID_HANDLE_VALUE) { DWORD BytesRead; ReadFile(File, Dest, Size, &BytesRead, 0); Assert(BytesRead == Size); } } static PLATFORM_WRITE_FILE(Win32_WriteFile) { HANDLE File = (HANDLE)Handle.Platform; if(File != INVALID_HANDLE_VALUE) { DWORD BytesWritten; WriteFile(File, Source, Size, &BytesWritten, 0); Assert(BytesWritten == Size); } } static PLATFORM_GET_FILE_SIZE(Win32_GetFileSize) { u64 Result = 0; HANDLE File = (HANDLE)Handle.Platform; if(File != INVALID_HANDLE_VALUE) { LARGE_INTEGER FileSize; GetFileSizeEx(File, &FileSize); Result = FileSize.QuadPart; } return(Result); } static PLATFORM_SET_CURSOR(Win32_SetCursor) { Global_Win32State.Cursor = Cursor; } inline u64 Win32_GetWallClock(void) { LARGE_INTEGER Query; QueryPerformanceCounter(&Query); u64 Result = Query.QuadPart; return(Result); } inline r64 Win32_GetSecondsElapsed(u64 Start, u64 End) { u64 Elapsed = End - Start; r64 Result = (r64)Elapsed/(r64)Global_Win32State.PerformanceFrequency; return(Result); } inline FILETIME Win32_GetLastWriteTime(char *Path) { FILETIME Result = {}; HANDLE File = CreateFileA(Path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(File) { FILETIME Creation, LastAccess, LastWrite; GetFileTime(File, &Creation, &LastAccess, &LastWrite); Result = LastWrite; CloseHandle(File); } return(Result); } static win32_loaded_code Win32_LoadCode(void) { win32_loaded_code Code = {}; win32_state *State = &Global_Win32State; if(CopyFile(State->DLLPath, State->TempDLLPath, FALSE)) { Code.LastWriteTime = Win32_GetLastWriteTime(State->DLLPath); Code.DLL = LoadLibraryA(State->TempDLLPath); if(Code.DLL) { Code.UpdateAndRender = (vn_update_and_render *)GetProcAddress(Code.DLL, "VN_UpdateAndRender"); if(Code.UpdateAndRender) { Code.IsValid = true; } } } return(Code); } static void Win32_UnloadCode(win32_loaded_code *Code) { if(Code->DLL) { FreeLibrary(Code->DLL); } *Code = {}; } static void Win32_UpdateCode(win32_loaded_code *Code) { win32_state *State = &Global_Win32State; FILETIME LastWriteTime = Win32_GetLastWriteTime(State->DLLPath); if(CompareFileTime(&Code->LastWriteTime, &LastWriteTime) != 0) { Win32_UnloadCode(Code); *Code = Win32_LoadCode(); } } static PLATFORM_TOGGLE_FULLSCREEN(Win32_ToggleFullscreen) { HWND Window = Global_Win32State.Window; DWORD Style = GetWindowLong(Window, GWL_STYLE); if(Style & WS_OVERLAPPEDWINDOW) { MONITORINFO MonitorInfo = {sizeof(MonitorInfo)}; if(GetWindowPlacement(Window, &Global_WindowPosition) && GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), &MonitorInfo)) { // sixten: This doesn't work when the window is maximized. One wordaround would be to set the // window to "normal" size using ShowWindow(Window, SW_SHOWNORMAL) but it looks *very* scuffed. SetWindowLong(Window, GWL_STYLE, Style & ~WS_OVERLAPPEDWINDOW); SetWindowPos(Window, HWND_TOP, MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top, MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } } else { SetWindowLong(Window, GWL_STYLE, Style | WS_OVERLAPPEDWINDOW); SetWindowPlacement(Window, &Global_WindowPosition); SetWindowPos(Window, 0, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_FRAMECHANGED); } } inline v2 Win32_GetMouseP(HWND Window) { POINT Point; GetCursorPos(&Point); ScreenToClient(Window, &Point); v2 Result = V2(Point.x, Point.y); return(Result); } inline v2 Win32_GetWindowDim(HWND Window) { RECT ClientRect; GetClientRect(Window, &ClientRect); v2 Result = V2(ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top); return(Result); } inline platform_modifiers Win32_GetModifiers(void) { platform_modifiers Modifiers = 0; if(GetKeyState(VK_CONTROL) & 0x8000) { Modifiers |= PlatformModifier_Ctrl; } if(GetKeyState(VK_SHIFT) & 0x8000) { Modifiers |= PlatformModifier_Shift; } if(GetKeyState(VK_MENU) & 0x8000) { Modifiers |= PlatformModifier_Alt; } return(Modifiers); } static LRESULT Win32_WindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam) { LRESULT Result = 0; win32_state *State = &Global_Win32State; temporary_memory Scratch = GetScratch(0, 0); platform_event *Event = 0; b32 ButtonIsUp = false; axis2 ScrollAxis = Axis2_Y; switch(Message) { case WM_CLOSE: { Event = PushStruct(&State->EventArena, platform_event); Event->Type = PlatformEvent_WindowClose; } break; case WM_WINDOWPOSCHANGED: { // TODO(casey): For now, we are setting the window styles in here // because sometimes Windows can reposition our window out of fullscreen // without going through our ToggleFullscreen(), and we want to put our // title bar and border back when it does! WINDOWPOS *NewPos = (WINDOWPOS *)LParam; b32 BecomingFullscreen = false; MONITORINFO MonitorInfo = {sizeof(MonitorInfo)}; if(GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), &MonitorInfo)) { s32 MonWidth = (MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left); s32 MonHeight = (MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top); BecomingFullscreen = ((MonitorInfo.rcMonitor.left == NewPos->x) && (MonitorInfo.rcMonitor.top == NewPos->y) && (MonWidth == NewPos->cx) && (MonHeight == NewPos->cy)); } DWORD OldStyle = GetWindowLong(Window, GWL_STYLE); DWORD FullscreenStyle = OldStyle & ~WS_OVERLAPPEDWINDOW; DWORD WindowedStyle = OldStyle | WS_OVERLAPPEDWINDOW; DWORD NewStyle = (BecomingFullscreen) ? FullscreenStyle : WindowedStyle; if(NewStyle != OldStyle) { SetWindowLong(Window, GWL_STYLE, NewStyle); } Result = DefWindowProcA(Window, Message, WParam, LParam); } break; case WM_MOUSEHWHEEL: { ScrollAxis = Axis2_X; } fallthrough; case WM_MOUSEWHEEL: { Event = PushStruct(&State->EventArena, platform_event); Event->Type = PlatformEvent_MouseScroll; Event->Scroll.E[ScrollAxis] = GET_WHEEL_DELTA_WPARAM(WParam) / 120.0; } break; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: { ButtonIsUp = true; } fallthrough; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: { platform_event_type Type = ButtonIsUp ? PlatformEvent_Release : PlatformEvent_Press; platform_key Key = Key_Invalid; switch(Message) { case WM_LBUTTONUP: case WM_LBUTTONDOWN: { Key = Key_MouseLeft; } break; case WM_MBUTTONUP: case WM_MBUTTONDOWN: { Key = Key_MouseMiddle; } break; case WM_RBUTTONUP: case WM_RBUTTONDOWN: { Key = Key_MouseRight; } break; } Event = PushStruct(&State->EventArena, platform_event); Event->Type = Type; Event->Key = Key; Event->P = Win32_GetMouseP(Window); } break; case WM_SYSKEYUP: case WM_KEYUP: { ButtonIsUp = true; } fallthrough; case WM_SYSKEYDOWN: case WM_KEYDOWN: { platform_event_type Type = ButtonIsUp ? PlatformEvent_Release : PlatformEvent_Press; u32 VKCode = (u32)WParam; platform_key Key = Key_Invalid; if(VKCode >= 'A' && VKCode <= 'Z') { Key = (platform_key)(Key_A + (VKCode - 'A')); } else if(VKCode >= VK_F1 && VKCode <= VK_F12) { Key = (platform_key)(Key_F1 + (VKCode - VK_F1)); } else if(VKCode == VK_LEFT) { Key = Key_Left; } else if(VKCode == VK_RIGHT) { Key = Key_Right; } else if(VKCode == VK_UP) { Key = Key_Up; } else if(VKCode == VK_DOWN) { Key = Key_Down; } else if(VKCode == VK_SPACE) { Key = Key_Space; } else if(VKCode == VK_PRIOR) { Key = Key_PageUp; } else if(VKCode == VK_NEXT) { Key = Key_PageDown; } else if(VKCode == VK_HOME) { Key = Key_Home; } else if(VKCode == VK_END) { Key = Key_End; } else if(VKCode == VK_BACK) { Key = Key_Backspace; } else if(VKCode == VK_DELETE) { Key = Key_Delete; } if(Key != Key_Invalid) { Event = PushStruct(&State->EventArena, platform_event); Event->Type = Type; Event->Key = Key; } if(!ButtonIsUp && (VKCode == VK_RETURN) && (LParam & (1 << 29))) { Win32_ToggleFullscreen(); } else { Result = DefWindowProc(Window, Message, WParam, LParam); } } break; case WM_CHAR: { u32 Codepoint = (u32)WParam; if(Codepoint == '\r') { Codepoint = '\n'; } if((Codepoint >= 32 && Codepoint != 127) || Codepoint == '\t' || Codepoint == '\n') { Event = PushStruct(&State->EventArena, platform_event); Event->Type = PlatformEvent_Text; Event->Codepoint = Codepoint; } } break; case WM_SETCURSOR: { range2_r32 WindowRect = {}; WindowRect.Max = Win32_GetWindowDim(Window); if(InRange(WindowRect, Win32_GetMouseP(Window))) { persist HCURSOR CursorTable[PlatformCursor_Count]; persist b32 CursorTableLoaded = false; if(!CursorTableLoaded) { CursorTable[PlatformCursor_Arrow] = LoadCursor(0, IDC_ARROW); CursorTable[PlatformCursor_Cross] = LoadCursor(0, IDC_CROSS); CursorTable[PlatformCursor_Hand] = LoadCursor(0, IDC_HAND); CursorTable[PlatformCursor_Help] = LoadCursor(0, IDC_HELP); CursorTable[PlatformCursor_IBeam] = LoadCursor(0, IDC_IBEAM); CursorTable[PlatformCursor_SlashedCircle] = LoadCursor(0, IDC_NO); CursorTable[PlatformCursor_ArrowAll] = LoadCursor(0, IDC_SIZEALL); CursorTable[PlatformCursor_ArrowNESW] = LoadCursor(0, IDC_SIZENESW); CursorTable[PlatformCursor_ArrowVertical] = LoadCursor(0, IDC_SIZENS); CursorTable[PlatformCursor_ArrowNWSE] = LoadCursor(0, IDC_SIZENWSE); CursorTable[PlatformCursor_ArrowHorizontal] = LoadCursor(0, IDC_SIZEWE); CursorTable[PlatformCursor_Wait] = LoadCursor(0, IDC_WAIT); CursorTableLoaded = true; } SetCursor(CursorTable[Global_Win32State.Cursor]); } else { DefWindowProc(Window, Message, WParam, LParam); } } break; default: { Result = DefWindowProc(Window, Message, WParam, LParam); } break; } if(Event) { Event->Modifiers = Win32_GetModifiers(); DLLInsertLast(State->EventList.First, State->EventList.Last, Event); } ReleaseScratch(Scratch); return(Result); } static void Win32_ProcessInput(vn_input *Input, HWND Window, r32 dtForFrame) { win32_state *State = &Global_Win32State; { if(State->EventArenaTemp.Arena) { EndTemporaryMemory(State->EventArenaTemp); } State->EventArenaTemp = BeginTemporaryMemory(&State->EventArena); } MSG Message; while(PeekMessage(&Message, Window, 0, 0, PM_REMOVE)) { TranslateMessage(&Message); DispatchMessageA(&Message); } Input->EventList = &State->EventList; v2 NewMouseP = Win32_GetMouseP(Window); v2 OldMouseP = Input->MouseP; Input->dMouseP = NewMouseP - OldMouseP; Input->MouseP = NewMouseP; Input->dtForFrame = dtForFrame; } static void Win32_EnforceFrameRate(u64 FrameBegin, r32 TargetFrameRate) { win32_state *State = &Global_Win32State; r64 TimeElapsed = Win32_GetSecondsElapsed(FrameBegin, Win32_GetWallClock()); r64 Target = 1.0 / TargetFrameRate; r64 ToSleep = Target - TimeElapsed; if(ToSleep > 0) { u64 MSToSleep = (u64)Floor(ToSleep*1000); if(State->SleepIsGranular) { Sleep(MSToSleep); } while(ToSleep > 0) { TimeElapsed = Win32_GetSecondsElapsed(FrameBegin, Win32_GetWallClock()); ToSleep = Target - TimeElapsed; } } } inline void Win32_GetRelevantPaths(win32_state *State) { GetModuleFileName(0, State->EXEPath, ArrayCount(State->EXEPath)); if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { Win32_PlatformError("Path to executable is too long. Try running the game from another directory", true); } string EXEPathString = MakeStringFromCString(State->EXEPath); s64 OnePastLastSlash = LastIndexOf(EXEPathString, '\\') + 1; s64 BuildIndex = LastIndexOf(EXEPathString, StrLit("build")); if(BuildIndex == -1) { State->ContentsPath = MakeString((char *)EXEPathString.Data, OnePastLastSlash); } else { State->ContentsPath = MakeString((char *)EXEPathString.Data, BuildIndex); } string DLLName = StrLit("vn.dll"); Copy(State->DLLPath, State->EXEPath, OnePastLastSlash); Copy(State->DLLPath+OnePastLastSlash, DLLName.Data, DLLName.Count); string TempDLLName = StrLit("temp_vn.dll"); Copy(State->TempDLLPath, State->EXEPath, OnePastLastSlash); Copy(State->TempDLLPath+OnePastLastSlash, TempDLLName.Data, TempDLLName.Count); } int WinMain(HINSTANCE Instance, HINSTANCE PreviousInstance, LPSTR CommandLine, int ShowCommand) { thread_context ThreadContext = {}; SetThreadContext(&ThreadContext); // sixten: Setup Win32 platform state. { win32_state *State = &Global_Win32State; Win32_GetRelevantPaths(State); LARGE_INTEGER FrequencyQuery; QueryPerformanceFrequency(&FrequencyQuery); State->PerformanceFrequency = FrequencyQuery.QuadPart; State->MemorySentinel.Next = &State->MemorySentinel; State->MemorySentinel.Prev = &State->MemorySentinel; State->SleepIsGranular = (timeBeginPeriod(1) == TIMERR_NOERROR); } // sixten: Setup platform layer { Platform.AllocateMemory = Win32_AllocateMemory; Platform.DeallocateMemory = Win32_DeallocateMemory; Platform.OpenFile = Win32_OpenFile; Platform.CloseFile = Win32_CloseFile; Platform.ReadFile = Win32_ReadFile; Platform.WriteFile = Win32_WriteFile; Platform.GetFileSize = Win32_GetFileSize; Platform.SetCursor = Win32_SetCursor; Platform.ToggleFullscreen = Win32_ToggleFullscreen; Platform.ShowMessage = Win32_ShowMessage; } WNDCLASS WindowClass = {}; WindowClass.lpszClassName = "vn-window-class"; WindowClass.lpfnWndProc = Win32_WindowCallback; WindowClass.hCursor = LoadCursorA(0, IDC_ARROW); WindowClass.style = CS_OWNDC; if(RegisterClassA(&WindowClass)) { HWND Window = CreateWindowEx(0, WindowClass.lpszClassName, "vn - June 2023 Build", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, Instance, 0); if(Window) { Global_Win32State.Window = Window; vn_input Input = {}; vn_render_commands RenderCommands = {}; // sixten: Setup OpenGL HDC DeviceContext = GetDC(Window); Win32_CreateOpenGLContext(DeviceContext); opengl_context OpenGLContext = OpenGL_SetupContext(&RenderCommands, 16*1024); vn_memory Memory = {}; Memory.PlatformAPI = Platform; win32_loaded_code LoadedCode = Win32_LoadCode(); ShowWindow(Window, SW_SHOWNORMAL); u64 CurrentTime = Win32_GetWallClock(); while(!Input.ExitRequested) { u64 NewTime = Win32_GetWallClock(); r64 dtForFrame = Win32_GetSecondsElapsed(CurrentTime, NewTime); CurrentTime = NewTime; Win32_ProcessInput(&Input, Window, dtForFrame); Win32_SetCursor(PlatformCursor_Arrow); // sixten: Update and render frame. { Win32_UpdateCode(&LoadedCode); OpenGL_BeginFrame(&RenderCommands, Win32_GetWindowDim(Window)); if(LoadedCode.IsValid) { LoadedCode.UpdateAndRender(&ThreadContext, &Memory, &Input, &RenderCommands); } b32 UseVSync = (Input.RefreshRate == 0); wglSwapIntervalEXT(UseVSync); OpenGL_EndFrame(&OpenGLContext, &RenderCommands); wglSwapLayerBuffers(DeviceContext, WGL_SWAP_MAIN_PLANE); } b32 ShouldLimitFrameRate = (Input.RefreshRate > 0); if(ShouldLimitFrameRate) { Win32_EnforceFrameRate(CurrentTime, Input.RefreshRate); } } } else { Win32_PlatformError("Unable to create window.", true); } } else { Win32_PlatformError("Unable to register window class.", true); } return(0); }