vn/code/win32_main.cpp

686 lines
21 KiB
C++

#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 b32 Global_Running;
global win32_state Global_Win32State;
global WINDOWPLACEMENT Global_WindowPosition = {sizeof(Global_WindowPosition)};;
static void Win32_PlatformError(char *Message, bool IsFatal)
{
MessageBoxA(0, Message, "nv - Platform Error", MB_OK|(IsFatal?MB_ICONSTOP:MB_ICONEXCLAMATION));
if(IsFatal)
{
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;
}
string FullPath = Path;
HANDLE File = CreateFileA((char *)Path.Data, DesiredAccess, 0, 0, CreationAttributes, 0, 0);
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_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))
{
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
{
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:
{
Global_Running = false;
} 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);
}
s64 OnePastLastSlash = LastIndexOf(MakeStringFromCString(State->EXEPath), '\\') + 1;
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.GetFileSize = Win32_GetFileSize;
Platform.SetCursor = Win32_SetCursor;
Platform.ToggleFullscreen = Win32_ToggleFullscreen;
}
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 = {};
// sixten: Setup OpenGL
vn_render_commands RenderCommands = {};
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();
Global_Running = true;
while(Global_Running)
{
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.TargetRefreshRate == 0);
wglSwapIntervalEXT(UseVSync);
OpenGL_EndFrame(&OpenGLContext, &RenderCommands);
wglSwapLayerBuffers(DeviceContext, WGL_SWAP_MAIN_PLANE);
}
b32 ShouldLimitFrameRate = (Input.TargetRefreshRate > 0);
if(ShouldLimitFrameRate)
{
Win32_EnforceFrameRate(CurrentTime, Input.TargetRefreshRate);
}
}
}
else
{
Win32_PlatformError("Unable to create window.", true);
}
}
else
{
Win32_PlatformError("Unable to register window class.", true);
}
return(0);
}