/* ======================================================================== $File: $ $Date: $ $Revision: $ $Creator: Casey Muratori $ $Notice: (C) Copyright 2014 by Molly Rocket, Inc. All Rights Reserved. $ ======================================================================== */ /* TODO(casey): THIS IS NOT A FINAL PLATFORM LAYER!!! - Saved game locations - Getting a handle to our own executable file - Asset loading path - Threading (launch a thread) - Raw Input (support for multiple keyboards) - Sleep/timeBeginPeriod - ClipCursor() (for multimonitor support) - Fullscreen support - WM_SETCURSOR (control cursor visibility) - QueryCancelAutoplay - WM_ACTIVATEAPP (for when we are not the active application) - Blit speed improvements (BitBlt) - Hardware acceleration (OpenGL or Direct3D or BOTH??) - GetKeyboardLayout (for French keyboards, international WASD support) Just a partial list of stuff!! */ #include "handmade.h" #include #include #include #include #include #include "win32_handmade.h" // TODO(casey): This is a global for now. global_variable b32 GlobalRunning; global_variable b32 GlobalPause; global_variable win32_offscreen_buffer GlobalBackbuffer; global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer; global_variable s64 GlobalPerfCountFrequency; // NOTE(casey): XInputGetState #define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState) typedef X_INPUT_GET_STATE(x_input_get_state); X_INPUT_GET_STATE(XInputGetStateStub) { return(ERROR_DEVICE_NOT_CONNECTED); } global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub; #define XInputGetState XInputGetState_ // NOTE(casey): XInputSetState #define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) typedef X_INPUT_SET_STATE(x_input_set_state); X_INPUT_SET_STATE(XInputSetStateStub) { return(ERROR_DEVICE_NOT_CONNECTED); } global_variable x_input_set_state *XInputSetState_ = XInputSetStateStub; #define XInputSetState XInputSetState_ #define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter) typedef DIRECT_SOUND_CREATE(direct_sound_create); internal void CatStrings(size_t SourceACount, char *SourceA, size_t SourceBCount, char *SourceB, size_t DestCount, char *Dest) { // TODO(casey): Dest bounds checking! for(int Index = 0; Index < SourceACount; ++Index) { *Dest++ = *SourceA++; } for(int Index = 0; Index < SourceBCount; ++Index) { *Dest++ = *SourceB++; } *Dest++ = 0; } internal void Win32GetEXEFileName(win32_state *State) { // NOTE(casey): Never use MAX_PATH in code that is user-facing, because it // can be dangerous and lead to bad results. DWORD SizeOfFilename = GetModuleFileNameA(0, State->EXEFileName, sizeof(State->EXEFileName)); State->OnePastLastEXEFileNameSlash = State->EXEFileName; for(char *Scan = State->EXEFileName; *Scan; ++Scan) { if(*Scan == '\\') { State->OnePastLastEXEFileNameSlash = Scan + 1; } } } internal int StringLength(char *String) { int Count = 0; while(*String++) { ++Count; } return(Count); } internal void Win32BuildEXEPathFileName(win32_state *State, char *FileName, int DestCount, char *Dest) { CatStrings(State->OnePastLastEXEFileNameSlash - State->EXEFileName, State->EXEFileName, StringLength(FileName), FileName, DestCount, Dest); } DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUGPlatformFreeFileMemory) { if(Memory) { VirtualFree(Memory, 0, MEM_RELEASE); } } DEBUG_PLATFORM_READ_ENTIRE_FILE(DEBUGPlatformReadEntireFile) { debug_read_file_result Result = {}; HANDLE FileHandle = CreateFileA(FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(FileHandle != INVALID_HANDLE_VALUE) { LARGE_INTEGER FileSize; if(GetFileSizeEx(FileHandle, &FileSize)) { u32 FileSize32 = SafeTruncateUInt64(FileSize.QuadPart); Result.Contents = VirtualAlloc(0, FileSize32, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); if(Result.Contents) { DWORD BytesRead; if(ReadFile(FileHandle, Result.Contents, FileSize32, &BytesRead, 0) && (FileSize32 == BytesRead)) { // NOTE(casey): File read successfully Result.ContentsSize = FileSize32; } else { // TODO(casey): Logging DEBUGPlatformFreeFileMemory(Thread, Result.Contents, Result.ContentsSize); Result.Contents = 0; } } else { // TODO(casey): Logging } } else { // TODO(casey): Logging } CloseHandle(FileHandle); } else { // TODO(casey): Logging } return(Result); } DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DEBUGPlatformWriteEntireFile) { b32 Result = false; HANDLE FileHandle = CreateFileA(FileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); if(FileHandle != INVALID_HANDLE_VALUE) { DWORD BytesWritten; if(WriteFile(FileHandle, Memory, MemorySize, &BytesWritten, 0)) { // NOTE(casey): File read successfully Result = (BytesWritten == MemorySize); } else { // TODO(casey): Logging } CloseHandle(FileHandle); } else { // TODO(casey): Logging } return(Result); } inline FILETIME Win32GetLastWriteTime(char *Filename) { FILETIME LastWriteTime = {}; WIN32_FILE_ATTRIBUTE_DATA Data; if(GetFileAttributesEx(Filename, GetFileExInfoStandard, &Data)) { LastWriteTime = Data.ftLastWriteTime; } return(LastWriteTime); } internal win32_game_code Win32LoadGameCode(char *SourceDLLName, char *TempDLLName) { win32_game_code Result = {}; // TODO(casey): Need to get the proper path here! // TODO(casey): Automatic determination of when updates are necessary. Result.DLLLastWriteTime = Win32GetLastWriteTime(SourceDLLName); CopyFile(SourceDLLName, TempDLLName, FALSE); Result.GameCodeDLL = LoadLibraryA(TempDLLName); if(Result.GameCodeDLL) { Result.UpdateAndRender = (game_update_and_render *) GetProcAddress(Result.GameCodeDLL, "GameUpdateAndRender"); Result.GetSoundSamples = (game_get_sound_samples *) GetProcAddress(Result.GameCodeDLL, "GameGetSoundSamples"); Result.IsValid = (Result.UpdateAndRender && Result.GetSoundSamples); } if(!Result.IsValid) { Result.UpdateAndRender = 0; Result.GetSoundSamples = 0; } return(Result); } internal void Win32UnloadGameCode(win32_game_code *GameCode) { if(GameCode->GameCodeDLL) { FreeLibrary(GameCode->GameCodeDLL); GameCode->GameCodeDLL = 0; } GameCode->IsValid = false; GameCode->UpdateAndRender = 0; GameCode->GetSoundSamples = 0; } internal void Win32LoadXInput(void) { // TODO(casey): Test this on Windows 8 HMODULE XInputLibrary = LoadLibraryA("xinput1_4.dll"); if(!XInputLibrary) { // TODO(casey): Diagnostic XInputLibrary = LoadLibraryA("xinput9_1_0.dll"); } if(!XInputLibrary) { // TODO(casey): Diagnostic XInputLibrary = LoadLibraryA("xinput1_3.dll"); } if(XInputLibrary) { XInputGetState = (x_input_get_state *)GetProcAddress(XInputLibrary, "XInputGetState"); if(!XInputGetState) {XInputGetState = XInputGetStateStub;} XInputSetState = (x_input_set_state *)GetProcAddress(XInputLibrary, "XInputSetState"); if(!XInputSetState) {XInputSetState = XInputSetStateStub;} // TODO(casey): Diagnostic } else { // TODO(casey): Diagnostic } } internal void Win32InitDSound(HWND Window, s32 SamplesPerSecond, s32 BufferSize) { // NOTE(casey): Load the library HMODULE DSoundLibrary = LoadLibraryA("dsound.dll"); if(DSoundLibrary) { // NOTE(casey): Get a DirectSound object! - cooperative direct_sound_create *DirectSoundCreate = (direct_sound_create *) GetProcAddress(DSoundLibrary, "DirectSoundCreate"); // TODO(casey): Double-check that this works on XP - DirectSound8 or 7?? LPDIRECTSOUND DirectSound; if(DirectSoundCreate && SUCCEEDED(DirectSoundCreate(0, &DirectSound, 0))) { WAVEFORMATEX WaveFormat = {}; WaveFormat.wFormatTag = WAVE_FORMAT_PCM; WaveFormat.nChannels = 2; WaveFormat.nSamplesPerSec = SamplesPerSecond; WaveFormat.wBitsPerSample = 16; WaveFormat.nBlockAlign = (WaveFormat.nChannels*WaveFormat.wBitsPerSample) / 8; WaveFormat.nAvgBytesPerSec = WaveFormat.nSamplesPerSec*WaveFormat.nBlockAlign; WaveFormat.cbSize = 0; if(SUCCEEDED(DirectSound->SetCooperativeLevel(Window, DSSCL_PRIORITY))) { DSBUFFERDESC BufferDescription = {}; BufferDescription.dwSize = sizeof(BufferDescription); BufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; // NOTE(casey): "Create" a primary buffer // TODO(casey): DSBCAPS_GLOBALFOCUS? LPDIRECTSOUNDBUFFER PrimaryBuffer; if(SUCCEEDED(DirectSound->CreateSoundBuffer(&BufferDescription, &PrimaryBuffer, 0))) { HRESULT Error = PrimaryBuffer->SetFormat(&WaveFormat); if(SUCCEEDED(Error)) { // NOTE(casey): We have finally set the format! OutputDebugStringA("Primary buffer format was set.\n"); } else { // TODO(casey): Diagnostic } } else { // TODO(casey): Diagnostic } } else { // TODO(casey): Diagnostic } // TODO(casey): DSBCAPS_GETCURRENTPOSITION2 DSBUFFERDESC BufferDescription = {}; BufferDescription.dwSize = sizeof(BufferDescription); BufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2; BufferDescription.dwBufferBytes = BufferSize; BufferDescription.lpwfxFormat = &WaveFormat; HRESULT Error = DirectSound->CreateSoundBuffer(&BufferDescription, &GlobalSecondaryBuffer, 0); if(SUCCEEDED(Error)) { OutputDebugStringA("Secondary buffer created successfully.\n"); } } else { // TODO(casey): Diagnostic } } else { // TODO(casey): Diagnostic } } internal win32_window_dimension Win32GetWindowDimension(HWND Window) { win32_window_dimension Result; RECT ClientRect; GetClientRect(Window, &ClientRect); Result.Width = ClientRect.right - ClientRect.left; Result.Height = ClientRect.bottom - ClientRect.top; return(Result); } internal void Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int Width, int Height) { // TODO(casey): Bulletproof this. // Maybe don't free first, free after, then free first if that fails. if(Buffer->Memory) { VirtualFree(Buffer->Memory, 0, MEM_RELEASE); } Buffer->Width = Width; Buffer->Height = Height; int BytesPerPixel = 4; Buffer->BytesPerPixel = BytesPerPixel; // NOTE(casey): When the biHeight field is negative, this is the clue to // Windows to treat this bitmap as top-down, not bottom-up, meaning that // the first three bytes of the image are the color for the top left pixel // in the bitmap, not the bottom left! Buffer->Info.bmiHeader.biSize = sizeof(Buffer->Info.bmiHeader); Buffer->Info.bmiHeader.biWidth = Buffer->Width; Buffer->Info.bmiHeader.biHeight = -Buffer->Height; Buffer->Info.bmiHeader.biPlanes = 1; Buffer->Info.bmiHeader.biBitCount = 32; Buffer->Info.bmiHeader.biCompression = BI_RGB; // NOTE(casey): Thank you to Chris Hecker of Spy Party fame // for clarifying the deal with StretchDIBits and BitBlt! // No more DC for us. int BitmapMemorySize = (Buffer->Width*Buffer->Height)*BytesPerPixel; Buffer->Memory = VirtualAlloc(0, BitmapMemorySize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); Buffer->Pitch = Width*BytesPerPixel; // TODO(casey): Probably clear this to black } internal void Win32DisplayBufferInWindow(win32_offscreen_buffer *Buffer, HDC DeviceContext, int WindowWidth, int WindowHeight) { // NOTE(casey): For prototyping purposes, we're going to always blit // 1-to-1 pixels to make sure we don't introduce artifacts with // stretching while we are learning to code the renderer! StretchDIBits(DeviceContext, 0, 0, Buffer->Width, Buffer->Height, 0, 0, Buffer->Width, Buffer->Height, Buffer->Memory, &Buffer->Info, DIB_RGB_COLORS, SRCCOPY); } internal LRESULT CALLBACK Win32MainWindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam) { LRESULT Result = 0; switch(Message) { case WM_CLOSE: { // TODO(casey): Handle this with a message to the user? GlobalRunning = false; } break; case WM_ACTIVATEAPP: { #if 0 if(WParam == TRUE) { SetLayeredWindowAttributes(Window, RGB(0, 0, 0), 255, LWA_ALPHA); } else { SetLayeredWindowAttributes(Window, RGB(0, 0, 0), 64, LWA_ALPHA); } #endif } break; case WM_DESTROY: { // TODO(casey): Handle this as an error - recreate window? GlobalRunning = false; } break; case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { Assert(!"Keyboard input came in through a non-dispatch message!"); } break; case WM_PAINT: { PAINTSTRUCT Paint; HDC DeviceContext = BeginPaint(Window, &Paint); win32_window_dimension Dimension = Win32GetWindowDimension(Window); Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, Dimension.Width, Dimension.Height); EndPaint(Window, &Paint); } break; default: { // OutputDebugStringA("default\n"); Result = DefWindowProcA(Window, Message, WParam, LParam); } break; } return(Result); } internal void Win32ClearBuffer(win32_sound_output *SoundOutput) { VOID *Region1; DWORD Region1Size; VOID *Region2; DWORD Region2Size; if(SUCCEEDED(GlobalSecondaryBuffer->Lock(0, SoundOutput->SecondaryBufferSize, &Region1, &Region1Size, &Region2, &Region2Size, 0))) { // TODO(casey): assert that Region1Size/Region2Size is valid u8 *DestSample = (u8 *)Region1; for(DWORD ByteIndex = 0; ByteIndex < Region1Size; ++ByteIndex) { *DestSample++ = 0; } DestSample = (u8 *)Region2; for(DWORD ByteIndex = 0; ByteIndex < Region2Size; ++ByteIndex) { *DestSample++ = 0; } GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size); } } internal void Win32FillSoundBuffer(win32_sound_output *SoundOutput, DWORD ByteToLock, DWORD BytesToWrite, game_sound_output_buffer *SourceBuffer) { // TODO(casey): More strenuous test! VOID *Region1; DWORD Region1Size; VOID *Region2; DWORD Region2Size; if(SUCCEEDED(GlobalSecondaryBuffer->Lock(ByteToLock, BytesToWrite, &Region1, &Region1Size, &Region2, &Region2Size, 0))) { // TODO(casey): assert that Region1Size/Region2Size is valid // TODO(casey): Collapse these two loops DWORD Region1SampleCount = Region1Size/SoundOutput->BytesPerSample; s16 *DestSample = (s16 *)Region1; s16 *SourceSample = SourceBuffer->Samples; for(DWORD SampleIndex = 0; SampleIndex < Region1SampleCount; ++SampleIndex) { *DestSample++ = *SourceSample++; *DestSample++ = *SourceSample++; ++SoundOutput->RunningSampleIndex; } DWORD Region2SampleCount = Region2Size/SoundOutput->BytesPerSample; DestSample = (s16 *)Region2; for(DWORD SampleIndex = 0; SampleIndex < Region2SampleCount; ++SampleIndex) { *DestSample++ = *SourceSample++; *DestSample++ = *SourceSample++; ++SoundOutput->RunningSampleIndex; } GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size); } } internal void Win32ProcessKeyboardMessage(game_button_state *NewState, b32 IsDown) { if(NewState->EndedDown != IsDown) { NewState->EndedDown = IsDown; ++NewState->HalfTransitionCount; } } internal void Win32ProcessXInputDigitalButton(DWORD XInputButtonState, game_button_state *OldState, DWORD ButtonBit, game_button_state *NewState) { NewState->EndedDown = ((XInputButtonState & ButtonBit) == ButtonBit); NewState->HalfTransitionCount = (OldState->EndedDown != NewState->EndedDown) ? 1 : 0; } internal r32 Win32ProcessXInputStickValue(SHORT Value, SHORT DeadZoneThreshold) { r32 Result = 0; if(Value < -DeadZoneThreshold) { Result = (r32)((Value + DeadZoneThreshold) / (32768.0f - DeadZoneThreshold)); } else if(Value > DeadZoneThreshold) { Result = (r32)((Value - DeadZoneThreshold) / (32767.0f - DeadZoneThreshold)); } return(Result); } internal void Win32GetInputFileLocation(win32_state *State, b32 InputStream, int SlotIndex, int DestCount, char *Dest) { char Temp[64]; wsprintf(Temp, "loop_edit_%d_%s.hmi", SlotIndex, InputStream ? "input" : "state"); Win32BuildEXEPathFileName(State, Temp, DestCount, Dest); } internal win32_replay_buffer * Win32GetReplayBuffer(win32_state *State, int unsigned Index) { Assert(Index < ArrayCount(State->ReplayBuffers)); win32_replay_buffer *Result = &State->ReplayBuffers[Index]; return(Result); } internal void Win32BeginRecordingInput(win32_state *State, int InputRecordingIndex) { win32_replay_buffer *ReplayBuffer = Win32GetReplayBuffer(State, InputRecordingIndex); if(ReplayBuffer->MemoryBlock) { State->InputRecordingIndex = InputRecordingIndex; char FileName[WIN32_STATE_FILE_NAME_COUNT]; Win32GetInputFileLocation(State, true, InputRecordingIndex, sizeof(FileName), FileName); State->RecordingHandle = CreateFileA(FileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); #if 0 LARGE_INTEGER FilePosition; FilePosition.QuadPart = State->TotalSize; SetFilePointerEx(State->RecordingHandle, FilePosition, 0, FILE_BEGIN); #endif CopyMemory(ReplayBuffer->MemoryBlock, State->GameMemoryBlock, State->TotalSize); } } internal void Win32EndRecordingInput(win32_state *State) { CloseHandle(State->RecordingHandle); State->InputRecordingIndex = 0; } internal void Win32BeginInputPlayBack(win32_state *State, int InputPlayingIndex) { win32_replay_buffer *ReplayBuffer = Win32GetReplayBuffer(State, InputPlayingIndex); if(ReplayBuffer->MemoryBlock) { State->InputPlayingIndex = InputPlayingIndex; char FileName[WIN32_STATE_FILE_NAME_COUNT]; Win32GetInputFileLocation(State, true, InputPlayingIndex, sizeof(FileName), FileName); State->PlaybackHandle = CreateFileA(FileName, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); #if 0 LARGE_INTEGER FilePosition; FilePosition.QuadPart = State->TotalSize; SetFilePointerEx(State->PlaybackHandle, FilePosition, 0, FILE_BEGIN); #endif CopyMemory(State->GameMemoryBlock, ReplayBuffer->MemoryBlock, State->TotalSize); } } internal void Win32EndInputPlayBack(win32_state *State) { CloseHandle(State->PlaybackHandle); State->InputPlayingIndex = 0; } internal void Win32RecordInput(win32_state *State, game_input *NewInput) { DWORD BytesWritten; WriteFile(State->RecordingHandle, NewInput, sizeof(*NewInput), &BytesWritten, 0); } internal void Win32PlayBackInput(win32_state *State, game_input *NewInput) { DWORD BytesRead = 0; if(ReadFile(State->PlaybackHandle, NewInput, sizeof(*NewInput), &BytesRead, 0)) { if(BytesRead == 0) { // NOTE(casey): We've hit the end of the stream, go back to the beginning int PlayingIndex = State->InputPlayingIndex; Win32EndInputPlayBack(State); Win32BeginInputPlayBack(State, PlayingIndex); ReadFile(State->PlaybackHandle, NewInput, sizeof(*NewInput), &BytesRead, 0); } } } internal void Win32ProcessPendingMessages(win32_state *State, game_controller_input *KeyboardController) { MSG Message; while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE)) { switch(Message.message) { case WM_QUIT: { GlobalRunning = false; } break; case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { u32 VKCode = (u32)Message.wParam; // NOTE(casey): Since we are comparing WasDown to IsDown, // we MUST use == and != to convert these bit tests to actual // 0 or 1 values. b32 WasDown = ((Message.lParam & (1 << 30)) != 0); b32 IsDown = ((Message.lParam & (1 << 31)) == 0); if(WasDown != IsDown) { if(VKCode == 'W') { Win32ProcessKeyboardMessage(&KeyboardController->MoveUp, IsDown); } else if(VKCode == 'A') { Win32ProcessKeyboardMessage(&KeyboardController->MoveLeft, IsDown); } else if(VKCode == 'R') { Win32ProcessKeyboardMessage(&KeyboardController->MoveDown, IsDown); } else if(VKCode == 'S') { Win32ProcessKeyboardMessage(&KeyboardController->MoveRight, IsDown); } else if(VKCode == 'Q') { Win32ProcessKeyboardMessage(&KeyboardController->LeftShoulder, IsDown); } else if(VKCode == 'E') { Win32ProcessKeyboardMessage(&KeyboardController->RightShoulder, IsDown); } else if(VKCode == VK_UP) { Win32ProcessKeyboardMessage(&KeyboardController->ActionUp, IsDown); } else if(VKCode == VK_LEFT) { Win32ProcessKeyboardMessage(&KeyboardController->ActionLeft, IsDown); } else if(VKCode == VK_DOWN) { Win32ProcessKeyboardMessage(&KeyboardController->ActionDown, IsDown); } else if(VKCode == VK_RIGHT) { Win32ProcessKeyboardMessage(&KeyboardController->ActionRight, IsDown); } else if(VKCode == VK_ESCAPE) { Win32ProcessKeyboardMessage(&KeyboardController->Start, IsDown); } else if(VKCode == VK_SPACE) { Win32ProcessKeyboardMessage(&KeyboardController->Back, IsDown); } #if HANDMADE_INTERNAL else if(VKCode == 'P') { if(IsDown) { GlobalPause = !GlobalPause; } } else if(VKCode == 'L') { if(IsDown) { if(State->InputPlayingIndex == 0) { if(State->InputRecordingIndex == 0) { Win32BeginRecordingInput(State, 1); } else { Win32EndRecordingInput(State); Win32BeginInputPlayBack(State, 1); } } else { Win32EndInputPlayBack(State); } } } #endif } b32 AltKeyWasDown = (Message.lParam & (1 << 29)); if((VKCode == VK_F4) && AltKeyWasDown) { GlobalRunning = false; } } break; default: { TranslateMessage(&Message); DispatchMessageA(&Message); } break; } } } inline LARGE_INTEGER Win32GetWallClock(void) { LARGE_INTEGER Result; QueryPerformanceCounter(&Result); return(Result); } inline r32 Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End) { r32 Result = ((r32)(End.QuadPart - Start.QuadPart) / (r32)GlobalPerfCountFrequency); return(Result); } #if 0 internal void Win32DebugDrawVertical(win32_offscreen_buffer *Backbuffer, int X, int Top, int Bottom, u32 Color) { if(Top <= 0) { Top = 0; } if(Bottom > Backbuffer->Height) { Bottom = Backbuffer->Height; } if((X >= 0) && (X < Backbuffer->Width)) { u8 *Pixel = ((u8 *)Backbuffer->Memory + X*Backbuffer->BytesPerPixel + Top*Backbuffer->Pitch); for(int Y = Top; Y < Bottom; ++Y) { *(u32 *)Pixel = Color; Pixel += Backbuffer->Pitch; } } } inline void Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer, win32_sound_output *SoundOutput, r32 C, int PadX, int Top, int Bottom, DWORD Value, u32 Color) { r32 XReal32 = (C * (r32)Value); int X = PadX + (int)XReal32; Win32DebugDrawVertical(Backbuffer, X, Top, Bottom, Color); } internal void Win32DebugSyncDisplay(win32_offscreen_buffer *Backbuffer, int MarkerCount, win32_debug_time_marker *Markers, int CurrentMarkerIndex, win32_sound_output *SoundOutput, r32 TargetSecondsPerFrame) { int PadX = 16; int PadY = 16; int LineHeight = 64; r32 C = (r32)(Backbuffer->Width - 2*PadX) / (r32)SoundOutput->SecondaryBufferSize; for(int MarkerIndex = 0; MarkerIndex < MarkerCount; ++MarkerIndex) { win32_debug_time_marker *ThisMarker = &Markers[MarkerIndex]; Assert(ThisMarker->OutputPlayCursor < SoundOutput->SecondaryBufferSize); Assert(ThisMarker->OutputWriteCursor < SoundOutput->SecondaryBufferSize); Assert(ThisMarker->OutputLocation < SoundOutput->SecondaryBufferSize); Assert(ThisMarker->OutputByteCount < SoundOutput->SecondaryBufferSize); Assert(ThisMarker->FlipPlayCursor < SoundOutput->SecondaryBufferSize); Assert(ThisMarker->FlipWriteCursor < SoundOutput->SecondaryBufferSize); DWORD PlayColor = 0xFFFFFFFF; DWORD WriteColor = 0xFFFF0000; DWORD ExpectedFlipColor = 0xFFFFFF00; DWORD PlayWindowColor = 0xFFFF00FF; int Top = PadY; int Bottom = PadY + LineHeight; if(MarkerIndex == CurrentMarkerIndex) { Top += LineHeight+PadY; Bottom += LineHeight+PadY; int FirstTop = Top; Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputPlayCursor, PlayColor); Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputWriteCursor, WriteColor); Top += LineHeight+PadY; Bottom += LineHeight+PadY; Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputLocation, PlayColor); Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputLocation + ThisMarker->OutputByteCount, WriteColor); Top += LineHeight+PadY; Bottom += LineHeight+PadY; Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, FirstTop, Bottom, ThisMarker->ExpectedFlipPlayCursor, ExpectedFlipColor); } Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->FlipPlayCursor, PlayColor); Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->FlipPlayCursor + 480*SoundOutput->BytesPerSample, PlayWindowColor); Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->FlipWriteCursor, WriteColor); } } #endif int CALLBACK WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CommandLine, int ShowCode) { win32_state Win32State = {}; LARGE_INTEGER PerfCountFrequencyResult; QueryPerformanceFrequency(&PerfCountFrequencyResult); GlobalPerfCountFrequency = PerfCountFrequencyResult.QuadPart; Win32GetEXEFileName(&Win32State); char SourceGameCodeDLLFullPath[WIN32_STATE_FILE_NAME_COUNT]; Win32BuildEXEPathFileName(&Win32State, "handmade.dll", sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath); char TempGameCodeDLLFullPath[WIN32_STATE_FILE_NAME_COUNT]; Win32BuildEXEPathFileName(&Win32State, "handmade_temp.dll", sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath); // NOTE(casey): Set the Windows scheduler granularity to 1ms // so that our Sleep() can be more granular. UINT DesiredSchedulerMS = 1; b32 SleepIsGranular = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR); Win32LoadXInput(); WNDCLASSA WindowClass = {}; Win32ResizeDIBSection(&GlobalBackbuffer, 1280, 720); WindowClass.style = CS_HREDRAW|CS_VREDRAW; WindowClass.lpfnWndProc = Win32MainWindowCallback; WindowClass.hInstance = Instance; // WindowClass.hIcon; WindowClass.lpszClassName = "HandmadeHeroWindowClass"; if(RegisterClassA(&WindowClass)) { HWND Window = CreateWindowExA( 0, // WS_EX_TOPMOST|WS_EX_LAYERED, WindowClass.lpszClassName, "Handmade Hero", WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, Instance, 0); if(Window) { win32_sound_output SoundOutput = {}; // TODO(casey): How do we reliably query on this on Windows? int MonitorRefreshHz = 60; HDC RefreshDC = GetDC(Window); int Win32RefreshRate = GetDeviceCaps(RefreshDC, VREFRESH); ReleaseDC(Window, RefreshDC); if(Win32RefreshRate > 1) { MonitorRefreshHz = Win32RefreshRate; } r32 GameUpdateHz = (MonitorRefreshHz / 2.0f); r32 TargetSecondsPerFrame = 1.0f / (r32)GameUpdateHz; // TODO(casey): Make this like sixty seconds? SoundOutput.SamplesPerSecond = 48000; SoundOutput.BytesPerSample = sizeof(s16)*2; SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond*SoundOutput.BytesPerSample; // TODO(casey): Actually compute this variance and see // what the lowest reasonable value is. SoundOutput.SafetyBytes = (int)(((r32)SoundOutput.SamplesPerSecond*(r32)SoundOutput.BytesPerSample / GameUpdateHz)/3.0f); Win32InitDSound(Window, SoundOutput.SamplesPerSecond, SoundOutput.SecondaryBufferSize); Win32ClearBuffer(&SoundOutput); GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); GlobalRunning = true; #if 0 // NOTE(casey): This tests the PlayCursor/WriteCursor update frequency // On the Handmade Hero machine, it was 480 samples. while(GlobalRunning) { DWORD PlayCursor; DWORD WriteCursor; GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor); char TextBuffer[256]; _snprintf_s(TextBuffer, sizeof(TextBuffer), "PC:%u WC:%u\n", PlayCursor, WriteCursor); OutputDebugStringA(TextBuffer); } #endif // TODO(casey): Pool with bitmap VirtualAlloc s16 *Samples = (s16 *)VirtualAlloc(0, SoundOutput.SecondaryBufferSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); #if HANDMADE_INTERNAL LPVOID BaseAddress = (LPVOID)Terabytes(2); #else LPVOID BaseAddress = 0; #endif game_memory GameMemory = {}; GameMemory.PermanentStorageSize = Megabytes(64); GameMemory.TransientStorageSize = Gigabytes(1); GameMemory.DEBUGPlatformFreeFileMemory = DEBUGPlatformFreeFileMemory; GameMemory.DEBUGPlatformReadEntireFile = DEBUGPlatformReadEntireFile; GameMemory.DEBUGPlatformWriteEntireFile = DEBUGPlatformWriteEntireFile; // TODO(casey): Handle various memory footprints (USING SYSTEM METRICS) // TODO(casey): Use MEM_LARGE_PAGES and call adjust token // privileges when not on Windows XP? Win32State.TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize; Win32State.GameMemoryBlock = VirtualAlloc(BaseAddress, (size_t)Win32State.TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); GameMemory.PermanentStorage = Win32State.GameMemoryBlock; GameMemory.TransientStorage = ((u8 *)GameMemory.PermanentStorage + GameMemory.PermanentStorageSize); for(int ReplayIndex = 0; ReplayIndex < ArrayCount(Win32State.ReplayBuffers); ++ReplayIndex) { win32_replay_buffer *ReplayBuffer = &Win32State.ReplayBuffers[ReplayIndex]; // TODO(casey): Recording system still seems to take too long // on record start - find out what Windows is doing and if // we can speed up / defer some of that processing. Win32GetInputFileLocation(&Win32State, false, ReplayIndex, sizeof(ReplayBuffer->FileName), ReplayBuffer->FileName); ReplayBuffer->FileHandle = CreateFileA(ReplayBuffer->FileName, GENERIC_WRITE|GENERIC_READ, 0, 0, CREATE_ALWAYS, 0, 0); LARGE_INTEGER MaxSize; MaxSize.QuadPart = Win32State.TotalSize; ReplayBuffer->MemoryMap = CreateFileMapping( ReplayBuffer->FileHandle, 0, PAGE_READWRITE, MaxSize.HighPart, MaxSize.LowPart, 0); ReplayBuffer->MemoryBlock = MapViewOfFile( ReplayBuffer->MemoryMap, FILE_MAP_ALL_ACCESS, 0, 0, Win32State.TotalSize); if(ReplayBuffer->MemoryBlock) { } else { // TODO(casey): Diagnostic } } if(Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage) { game_input Input[2] = {}; game_input *NewInput = &Input[0]; game_input *OldInput = &Input[1]; LARGE_INTEGER LastCounter = Win32GetWallClock(); LARGE_INTEGER FlipWallClock = Win32GetWallClock(); int DebugTimeMarkerIndex = 0; win32_debug_time_marker DebugTimeMarkers[30] = {0}; DWORD AudioLatencyBytes = 0; r32 AudioLatencySeconds = 0; b32 SoundIsValid = false; win32_game_code Game = Win32LoadGameCode(SourceGameCodeDLLFullPath, TempGameCodeDLLFullPath); u32 LoadCounter = 0; s64 LastCycleCount = __rdtsc(); while(GlobalRunning) { FILETIME NewDLLWriteTime = Win32GetLastWriteTime(SourceGameCodeDLLFullPath); if(CompareFileTime(&NewDLLWriteTime, &Game.DLLLastWriteTime) != 0) { Win32UnloadGameCode(&Game); Game = Win32LoadGameCode(SourceGameCodeDLLFullPath, TempGameCodeDLLFullPath); LoadCounter = 0; } // TODO(casey): Zeroing macro // TODO(casey): We can't zero everything because the up/down state will // be wrong!!! game_controller_input *OldKeyboardController = GetController(OldInput, 0); game_controller_input *NewKeyboardController = GetController(NewInput, 0); *NewKeyboardController = {}; NewKeyboardController->IsConnected = true; for(int ButtonIndex = 0; ButtonIndex < ArrayCount(NewKeyboardController->Buttons); ++ButtonIndex) { NewKeyboardController->Buttons[ButtonIndex].EndedDown = OldKeyboardController->Buttons[ButtonIndex].EndedDown; } Win32ProcessPendingMessages(&Win32State, NewKeyboardController); if(!GlobalPause) { POINT MouseP; GetCursorPos(&MouseP); ScreenToClient(Window, &MouseP); NewInput->MouseX = MouseP.x; NewInput->MouseY = MouseP.y; NewInput->MouseZ = 0; // TODO(casey): Support mousewheel? Win32ProcessKeyboardMessage(&NewInput->MouseButtons[0], GetKeyState(VK_LBUTTON) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[1], GetKeyState(VK_MBUTTON) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[2], GetKeyState(VK_RBUTTON) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[3], GetKeyState(VK_XBUTTON1) & (1 << 15)); Win32ProcessKeyboardMessage(&NewInput->MouseButtons[4], GetKeyState(VK_XBUTTON2) & (1 << 15)); // TODO(casey): Need to not poll disconnected controllers to avoid // xinput frame rate hit on older libraries... // TODO(casey): Should we poll this more frequently DWORD MaxControllerCount = XUSER_MAX_COUNT; if(MaxControllerCount > (ArrayCount(NewInput->Controllers) - 1)) { MaxControllerCount = (ArrayCount(NewInput->Controllers) - 1); } for (DWORD ControllerIndex = 0; ControllerIndex < MaxControllerCount; ++ControllerIndex) { DWORD OurControllerIndex = ControllerIndex + 1; game_controller_input *OldController = GetController(OldInput, OurControllerIndex); game_controller_input *NewController = GetController(NewInput, OurControllerIndex); XINPUT_STATE ControllerState; if(XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS) { NewController->IsConnected = true; NewController->IsAnalog = OldController->IsAnalog; // NOTE(casey): This controller is plugged in // TODO(casey): See if ControllerState.dwPacketNumber increments too rapidly XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad; // TODO(casey): This is a square deadzone, check XInput to // verify that the deadzone is "round" and show how to do // round deadzone processing. NewController->StickAverageX = Win32ProcessXInputStickValue( Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); NewController->StickAverageY = Win32ProcessXInputStickValue( Pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); if((NewController->StickAverageX != 0.0f) || (NewController->StickAverageY != 0.0f)) { NewController->IsAnalog = true; } if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_UP) { NewController->StickAverageY = 1.0f; NewController->IsAnalog = false; } if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { NewController->StickAverageY = -1.0f; NewController->IsAnalog = false; } if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) { NewController->StickAverageX = -1.0f; NewController->IsAnalog = false; } if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) { NewController->StickAverageX = 1.0f; NewController->IsAnalog = false; } r32 Threshold = 0.5f; Win32ProcessXInputDigitalButton( (NewController->StickAverageX < -Threshold) ? 1 : 0, &OldController->MoveLeft, 1, &NewController->MoveLeft); Win32ProcessXInputDigitalButton( (NewController->StickAverageX > Threshold) ? 1 : 0, &OldController->MoveRight, 1, &NewController->MoveRight); Win32ProcessXInputDigitalButton( (NewController->StickAverageY < -Threshold) ? 1 : 0, &OldController->MoveDown, 1, &NewController->MoveDown); Win32ProcessXInputDigitalButton( (NewController->StickAverageY > Threshold) ? 1 : 0, &OldController->MoveUp, 1, &NewController->MoveUp); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionDown, XINPUT_GAMEPAD_A, &NewController->ActionDown); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionRight, XINPUT_GAMEPAD_B, &NewController->ActionRight); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionLeft, XINPUT_GAMEPAD_X, &NewController->ActionLeft); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->ActionUp, XINPUT_GAMEPAD_Y, &NewController->ActionUp); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->LeftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER, &NewController->LeftShoulder); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->RightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER, &NewController->RightShoulder); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Start, XINPUT_GAMEPAD_START, &NewController->Start); Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->Back, XINPUT_GAMEPAD_BACK, &NewController->Back); } else { // NOTE(casey): The controller is not available NewController->IsConnected = false; } } thread_context Thread = {}; game_offscreen_buffer Buffer = {}; Buffer.Memory = GlobalBackbuffer.Memory; Buffer.Width = GlobalBackbuffer.Width; Buffer.Height = GlobalBackbuffer.Height; Buffer.Pitch = GlobalBackbuffer.Pitch; Buffer.BytesPerPixel = GlobalBackbuffer.BytesPerPixel; if(Win32State.InputRecordingIndex) { Win32RecordInput(&Win32State, NewInput); } if(Win32State.InputPlayingIndex) { Win32PlayBackInput(&Win32State, NewInput); } if(Game.UpdateAndRender) { Game.UpdateAndRender(&Thread, &GameMemory, NewInput, &Buffer); } LARGE_INTEGER AudioWallClock = Win32GetWallClock(); r32 FromBeginToAudioSeconds = Win32GetSecondsElapsed(FlipWallClock, AudioWallClock); DWORD PlayCursor; DWORD WriteCursor; if(GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) { /* NOTE(casey): Here is how sound output computation works. We define a safety value that is the number of samples we think our game update loop may vary by (let's say up to 2ms) When we wake up to write audio, we will look and see what the play cursor position is and we will forecast ahead where we think the play cursor will be on the next frame boundary. We will then look to see if the write cursor is before that by at least our safety value. If it is, the target fill position is that frame boundary plus one frame. This gives us perfect audio sync in the case of a card that has low enough latency. If the write cursor is _after_ that safety margin, then we assume we can never sync the audio perfectly, so we will write one frame's worth of audio plus the safety margin's worth of guard samples. */ if(!SoundIsValid) { SoundOutput.RunningSampleIndex = WriteCursor / SoundOutput.BytesPerSample; SoundIsValid = true; } DWORD ByteToLock = ((SoundOutput.RunningSampleIndex*SoundOutput.BytesPerSample) % SoundOutput.SecondaryBufferSize); DWORD ExpectedSoundBytesPerFrame = (int)((r32)(SoundOutput.SamplesPerSecond*SoundOutput.BytesPerSample) / GameUpdateHz); r32 SecondsLeftUntilFlip = (TargetSecondsPerFrame - FromBeginToAudioSeconds); DWORD ExpectedBytesUntilFlip = (DWORD)((SecondsLeftUntilFlip/TargetSecondsPerFrame)*(r32)ExpectedSoundBytesPerFrame); DWORD ExpectedFrameBoundaryByte = PlayCursor + ExpectedBytesUntilFlip; DWORD SafeWriteCursor = WriteCursor; if(SafeWriteCursor < PlayCursor) { SafeWriteCursor += SoundOutput.SecondaryBufferSize; } Assert(SafeWriteCursor >= PlayCursor); SafeWriteCursor += SoundOutput.SafetyBytes; b32 AudioCardIsLowLatency = (SafeWriteCursor < ExpectedFrameBoundaryByte); DWORD TargetCursor = 0; if(AudioCardIsLowLatency) { TargetCursor = (ExpectedFrameBoundaryByte + ExpectedSoundBytesPerFrame); } else { TargetCursor = (WriteCursor + ExpectedSoundBytesPerFrame + SoundOutput.SafetyBytes); } TargetCursor = (TargetCursor % SoundOutput.SecondaryBufferSize); DWORD BytesToWrite = 0; if(ByteToLock > TargetCursor) { BytesToWrite = (SoundOutput.SecondaryBufferSize - ByteToLock); BytesToWrite += TargetCursor; } else { BytesToWrite = TargetCursor - ByteToLock; } game_sound_output_buffer SoundBuffer = {}; SoundBuffer.SamplesPerSecond = SoundOutput.SamplesPerSecond; SoundBuffer.SampleCount = BytesToWrite / SoundOutput.BytesPerSample; SoundBuffer.Samples = Samples; if(Game.GetSoundSamples) { Game.GetSoundSamples(&Thread, &GameMemory, &SoundBuffer); } #if HANDMADE_INTERNAL win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex]; Marker->OutputPlayCursor = PlayCursor; Marker->OutputWriteCursor = WriteCursor; Marker->OutputLocation = ByteToLock; Marker->OutputByteCount = BytesToWrite; Marker->ExpectedFlipPlayCursor = ExpectedFrameBoundaryByte; DWORD UnwrappedWriteCursor = WriteCursor; if(UnwrappedWriteCursor < PlayCursor) { UnwrappedWriteCursor += SoundOutput.SecondaryBufferSize; } AudioLatencyBytes = UnwrappedWriteCursor - PlayCursor; AudioLatencySeconds = (((r32)AudioLatencyBytes / (r32)SoundOutput.BytesPerSample) / (r32)SoundOutput.SamplesPerSecond); #if 0 char TextBuffer[256]; _snprintf_s(TextBuffer, sizeof(TextBuffer), "BTL:%u TC:%u BTW:%u - PC:%u WC:%u DELTA:%u (%fs)\n", ByteToLock, TargetCursor, BytesToWrite, PlayCursor, WriteCursor, AudioLatencyBytes, AudioLatencySeconds); OutputDebugStringA(TextBuffer); #endif #endif Win32FillSoundBuffer(&SoundOutput, ByteToLock, BytesToWrite, &SoundBuffer); } else { SoundIsValid = false; } LARGE_INTEGER WorkCounter = Win32GetWallClock(); r32 WorkSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter); // TODO(casey): NOT TESTED YET! PROBABLY BUGGY!!!!! r32 SecondsElapsedForFrame = WorkSecondsElapsed; if(SecondsElapsedForFrame < TargetSecondsPerFrame) { if(SleepIsGranular) { DWORD SleepMS = (DWORD)(1000.0f * (TargetSecondsPerFrame - SecondsElapsedForFrame)); if(SleepMS > 0) { Sleep(SleepMS); } } r32 TestSecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock()); if(TestSecondsElapsedForFrame < TargetSecondsPerFrame) { // TODO(casey): LOG MISSED SLEEP HERE } while(SecondsElapsedForFrame < TargetSecondsPerFrame) { SecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock()); } } else { // TODO(casey): MISSED FRAME RATE! // TODO(casey): Logging } LARGE_INTEGER EndCounter = Win32GetWallClock(); r32 MSPerFrame = 1000.0f*Win32GetSecondsElapsed(LastCounter, EndCounter); LastCounter = EndCounter; win32_window_dimension Dimension = Win32GetWindowDimension(Window); HDC DeviceContext = GetDC(Window); Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, Dimension.Width, Dimension.Height); ReleaseDC(Window, DeviceContext); FlipWallClock = Win32GetWallClock(); #if HANDMADE_INTERNAL // NOTE(casey): This is debug code { DWORD PlayCursor; DWORD WriteCursor; if(GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) { Assert(DebugTimeMarkerIndex < ArrayCount(DebugTimeMarkers)); win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex]; Marker->FlipPlayCursor = PlayCursor; Marker->FlipWriteCursor = WriteCursor; } } #endif game_input *Temp = NewInput; NewInput = OldInput; OldInput = Temp; // TODO(casey): Should I clear these here? #if 1 u64 EndCycleCount = __rdtsc(); u64 CyclesElapsed = EndCycleCount - LastCycleCount; LastCycleCount = EndCycleCount; r64 FPS = 0.0f; r64 MCPF = ((r64)CyclesElapsed / (1000.0f * 1000.0f)); char FPSBuffer[256]; _snprintf_s(FPSBuffer, sizeof(FPSBuffer), "%.02fms/f, %.02ff/s, %.02fmc/f\n", MSPerFrame, FPS, MCPF); OutputDebugStringA(FPSBuffer); #endif #if HANDMADE_INTERNAL ++DebugTimeMarkerIndex; if(DebugTimeMarkerIndex == ArrayCount(DebugTimeMarkers)) { DebugTimeMarkerIndex = 0; } #endif } } } else { // TODO(casey): Logging } } else { // TODO(casey): Logging } } else { // TODO(casey): Logging } return(0); }