#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include "windows.h" #include "timeapi.h" #include "vn_platform.h" #if W32_LINK_SINGLE #include "vn.cpp" #endif #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 u64 Win32_GetPageSize(void) { SYSTEM_INFO Info; GetSystemInfo(&Info); return(Info.dwPageSize); } static PLATFORM_RESERVE(Win32_Reserve) { u64 GigabyteAlignedSize = Size+Gigabytes(1)-1; GigabyteAlignedSize -= GigabyteAlignedSize%Gigabytes(1); void *Result = VirtualAlloc(0, GigabyteAlignedSize, MEM_RESERVE, PAGE_NOACCESS); return(Result); } static PLATFORM_RELEASE(Win32_Release) { VirtualFree(Pointer, 0, MEM_RELEASE); } static PLATFORM_COMMIT(Win32_Commit) { u64 PageAlignedSize = Size+Win32_GetPageSize()-1; PageAlignedSize -= PageAlignedSize%Win32_GetPageSize(); VirtualAlloc(Pointer, PageAlignedSize, MEM_COMMIT, PAGE_READWRITE); } static PLATFORM_DECOMMIT(Win32_Decommit) { VirtualFree(Pointer, Size, MEM_DECOMMIT); } static PLATFORM_ALLOCATE(Win32_Allocate) { void *Result = VirtualAlloc(0, Size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); return(Result); } static PLATFORM_DEALLOCATE(Win32_Deallocate) { VirtualFree(Pointer, 0, MEM_DECOMMIT|MEM_RELEASE); } 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; } static PLATFORM_BEGIN_FILE_ITER(Win32_BeginFileIter) { win32_file_find_data *FileFindData = PushStruct(Arena, win32_file_find_data); temporary_memory Scratch = GetScratch(&Arena, 1); Path = PushFormat(Scratch.Arena, "%S/%S*", Global_Win32State.ContentsPath, Path); string16 Path16 = String16FromString8(Scratch.Arena, Path); FileFindData->Handle = FindFirstFileW((WCHAR *)Path16.Data, &FileFindData->FindData); ReleaseScratch(Scratch); platform_file_iter *Iter = (platform_file_iter *)FileFindData; return(Iter); } static PLATFORM_ADVANCE_FILE_ITER(Win32_AdvanceFileIter) { b32 Result = false; win32_file_find_data *FileFindData = (win32_file_find_data *)Iter; WIN32_FIND_DATAW FindData = {}; s64 InvalidCount = 0; for(;;) { if(FileFindData->FindFirstReturned) { Result = FindNextFileW(FileFindData->Handle, &FindData); } else { Result = (FileFindData->Handle != 0 && FileFindData->Handle != INVALID_HANDLE_VALUE); FindData = FileFindData->FindData; FileFindData->FindFirstReturned = true; } b32 NameIsInvalid = (FindData.cFileName[0] == '.' && (FindData.cFileName[1] == 0 || FindData.cFileName[1] == '.')); if(NameIsInvalid && InvalidCount < 10) { InvalidCount += 1; continue; } break; } if(Result != 0) { string16 Name16 = {}; Name16.Data = (u16 *)FindData.cFileName; for(u16 Index = 0; Index < MAX_PATH; Index += 1) { if(FindData.cFileName[Index] == 0) { break; } Name16.Count += 1; } *OutInfo = {}; OutInfo->Name = String8FromString16(Arena, Name16); OutInfo->IsDirectory = FindData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY; } return(Result); } static PLATFORM_END_FILE_ITER(Win32_EndFileIter) { win32_file_find_data *FileFindData = (win32_file_find_data *)Iter; FindClose(FileFindData->Handle); } 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); } #if !W32_LINK_SINGLE 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(); // sixten(NOTE): Sometimes the program decides to crash upon reloads, so we just wait for those to be over... Sleep(500); } } #endif 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(NOTE): 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); } } static PLATFORM_SET_CLIPBOARD(Win32_SetClipboard) { temporary_memory Scratch = GetScratch(); if(OpenClipboard(0)) { EmptyClipboard(); string16 String16 = String16FromString8(Scratch.Arena, String); HANDLE CopyHandle = GlobalAlloc(GMEM_MOVEABLE, String16.Count*sizeof(u16)+1); if(CopyHandle) { u16 *CopyBuffer = (u16 *)GlobalLock(CopyHandle); Copy(CopyBuffer, String16.Data, String16.Count*sizeof(u16)); CopyBuffer[String.Count] = 0; GlobalUnlock(CopyHandle); SetClipboardData(CF_UNICODETEXT, CopyHandle); } CloseClipboard(); } ReleaseScratch(Scratch); } static PLATFORM_GET_CLIPBOARD(Win32_GetClipboard) { string Result = {}; if(IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(0)) { HANDLE DataHandle = GetClipboardData(CF_UNICODETEXT); if(DataHandle) { u16 *Data = (u16 *)GlobalLock(DataHandle); if(Data) { s64 Count = StringLength16(Data); Result = String8FromString16(Arena, MakeString16(Data, Count)); GlobalUnlock(DataHandle); } } CloseClipboard(); } return(Result); } 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_RETURN) { Key = Key_Return; } 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; } else if(VKCode == VK_ESCAPE) { Key = Key_Escape; } 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; ArenaClear(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 = MakeString(State->EXEPath); s64 OnePastLastSlash = LastIndexOf(EXEPathString, '\\') + 1; s64 BuildIndex = LastIndexOf(EXEPathString, StrLit("\\build\\")) + 1; if(BuildIndex == 0) { BuildIndex = LastIndexOf(EXEPathString, StrLit("//build//")) + 1; } if(BuildIndex == 0) { State->ContentsPath = MakeString(EXEPathString.Data, OnePastLastSlash); } else { State->ContentsPath = MakeString(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) { RegisterPlatformFunctions(Win32); thread_context ThreadContext = AllocateThreadContext(); 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->EventArena = ArenaAlloc(Gigabytes(1)); State->SleepIsGranular = (timeBeginPeriod(1) == TIMERR_NOERROR); } 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 - October 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; #if !W32_LINK_SINGLE win32_loaded_code LoadedCode = Win32_LoadCode(); #endif 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. { v2_r32 RenderDim = Win32_GetWindowDim(Window); OpenGL_BeginFrame(&RenderCommands, RenderDim); #if W32_LINK_SINGLE VN_UpdateAndRender(&ThreadContext, &Memory, &Input, &RenderCommands); #else Win32_UpdateCode(&LoadedCode); if(LoadedCode.IsValid) { LoadedCode.UpdateAndRender(&ThreadContext, &Memory, &Input, &RenderCommands); } #endif 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); } #if VN_ASAN_ENABLED int main(int ArgumentCount, char **Arguments) { return WinMain(GetModuleHandle(0), 0, 0, 0); } #endif