Advertisement
Guest User

win32_handmade.cpp

a guest
Oct 14th, 2018
376
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 48.60 KB | None | 0 0
  1. /* This is not a final platform layer, not for shipping! Below is a partial list of stuff to implement.
  2.    - Figure out save game locations.
  3.    - Getting a handle to our own executable file.
  4.    - Need asset loading path.
  5.    - Multithreading for multiple cores.
  6.    - Support raw input, for multiple keyboards.
  7.    - Sleep and timeBeginPeriod for laptops.
  8.    - Clip cursor for multi-monitor support.
  9.    - Fullscreen support.
  10.    - WM_SETCURSOR for cursor visibility.
  11.    - QueryCancelAutoplay.
  12.    - WM_ACTIVATEAPP
  13.    - Blit speed improvements to BitBlt.
  14.    - Support hardware acceleration.
  15.    - GetKeyboardLayout for international support.
  16.  */
  17.  
  18. #include "handmade.h"
  19.  
  20. #include <windows.h>
  21. #include <stdio.h>
  22. #include <malloc.h>
  23. #include <xinput.h>
  24. #include <dsound.h>
  25. #include <math.h>
  26.  
  27. #include "win32_handmade.h"
  28.  
  29. // Here be dragons.
  30. // DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE* pState) is the function prototype provided in <Xinput.h> for XInputGetState().
  31. // By using the macro X_INPUT_GET_STATE(name), we can generate an arbitrary number of function signatures of the form DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE* pState) and swap in the given (name).
  32. #define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE* pState)
  33. #define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration)
  34. #define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter)
  35.  
  36. // Now, we need to define a type that are of the function signatures given above.
  37. // x_input_get_state and x_input_set_state are the names we want our macro to take on. At this stage, we should have the following.
  38. // a) WORD WINAPI x_input_get_state(DWORD dwUserIndex, XINPUT_STATE* pState) AND b) DWORD WINAPI x_input_set_state(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration).
  39. typedef X_INPUT_GET_STATE(x_input_get_state);
  40. typedef X_INPUT_SET_STATE(x_input_set_state);
  41. typedef DIRECT_SOUND_CREATE(direct_sound_create);
  42.  
  43. // Do the exact same thing to generate it again with different names, which makes the actual header for *OUR* functions. In essence, we have replaced the function prototype provided by <Xinput.h>.
  44. X_INPUT_GET_STATE(XInputGetStateStub) {
  45.     return ERROR_DEVICE_NOT_CONNECTED;
  46. }
  47.  
  48. X_INPUT_SET_STATE(XInputSetStateStub) {
  49.     return ERROR_DEVICE_NOT_CONNECTED;
  50. }
  51.  
  52. // Make globals that point to the functions above so that we can actually refer to them.
  53. // Since we never need to refer to sound once it's initialized, there's no reason to create a global pointer for it.
  54. global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
  55. global_variable x_input_set_state *XInputSetState_ = XInputSetStateStub;
  56.  
  57. // Any time XInputSetState and XInputGetState are used from now on, they actually refer to the pointers we declared above.
  58. #define XInputSetState XInputSetState_
  59. #define XInputGetState XInputGetState_
  60.  
  61. // These are globals unrelated to the macro redefinitons above.
  62. global_variable bool globalRunning;
  63. global_variable bool globalPause;
  64. global_variable win32_offscreen_buffer GlobalBackbuffer;
  65. global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer;
  66. global_variable int64_t globalPerformanceCounterFrequency;
  67.  
  68. // Free the memory occupied by a file.
  69. DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUGPlatformFreeFileMemory)
  70. {
  71.     if (Memory)
  72.         VirtualFree(Memory, 0, MEM_RELEASE);
  73.  
  74. }
  75.  
  76. // Hey, load a file.
  77. DEBUG_PLATFORM_READ_ENTIRE_FILE(DEBUGPlatformReadEntireFile)
  78. {
  79.     // Store the result of the call and the size of the file.
  80.     debug_read_file_result Result = {};
  81.  
  82.     // Get a handle to the file, specify r/w permissons for other processes, and always only open a file that already exists.
  83.     HANDLE FileHandle = CreateFileA(Filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0 , 0);
  84.     if (FileHandle != INVALID_HANDLE_VALUE) {
  85.  
  86.         // Determine the file size. For a 64 bit, use the modern GetFileSizeEx() which returns a 64-bit value with no alternations.
  87.         LARGE_INTEGER FileSize;
  88.         if (GetFileSizeEx(FileHandle, &FileSize)) {
  89.  
  90.             // Converts a 64-bit value to a 32-bit value.
  91.             uint32_t FileSize32 = SafeTruncateUInt64(FileSize.QuadPart);
  92.  
  93.             // Allocate the 64-bit part of the file size.
  94.             Result.Contents = VirtualAlloc(0, FileSize32, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  95.  
  96.             // If we got the memory, read into it. Doesn't take a 64-bit value for the bytes to read.
  97.             // Make sure we actually read back the entire size of the file, since another process potentially could modify the size?
  98.             DWORD BytesRead;
  99.             if (ReadFile(FileHandle, Result.Contents, FileSize32, &BytesRead, 0) && (FileSize32 == BytesRead)) {
  100.                 Result.contentSize = FileSize32;
  101.             }
  102.  
  103.             // Else, handle the error case when we couldn't read the file.
  104.             else
  105.             {
  106.                 // Free the memory we had immediately, as if we never allocated it.
  107.                 DEBUGPlatformFreeFileMemory(Result.Contents);
  108.                 Result.Contents = 0;
  109.             }
  110.         }
  111.  
  112.         // Logging: The file size was not determined.
  113.         else {
  114.  
  115.         }
  116.     }
  117.  
  118.     // Loggging: The file handle was not initialized. Error with reading the file.
  119.     else {
  120.  
  121.     }
  122.  
  123.     // At the very end of the call, close the file handle.
  124.     CloseHandle(FileHandle);
  125.    
  126.     return Result;
  127. }
  128.  
  129. DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DEBUGPlatformWriteEntireFile)
  130. {
  131.     // Initally, the file has not yet been written.
  132.     bool Result = false;
  133.  
  134.     // Get a handle to the file and always create a new file or overwrite old files.
  135.     HANDLE FileHandle = CreateFileA(Filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
  136.     if (FileHandle != INVALID_HANDLE_VALUE) {
  137.          
  138.         // If we got the memory, write into it. Doesn't take a 64-bit value for the bytes to read.
  139.         // Make sure we actually read back the entire size of the file, since another process potentially could modify the size?
  140.         DWORD BytesWritten;
  141.         if (WriteFile(FileHandle, Memory, memorySize, &BytesWritten, 0)) {
  142.             Result = (BytesWritten == memorySize);
  143.         }
  144.         // Else, handle the error case when we couldn't read the file.
  145.         else
  146.         {
  147.    
  148.         }
  149.  
  150.         // At the very end of the call, close the file handle.
  151.         CloseHandle(FileHandle);
  152.  
  153.     }
  154.     // Loggging: The file handle was not initialized. Error with reading the file.
  155.     else {
  156.  
  157.     }
  158.  
  159.     // If everything succeeds, this should return true.
  160.     return Result;
  161. }
  162.  
  163.  
  164. // Contains pointers to our game functions.
  165. struct win32_game_code
  166. {
  167.     HMODULE GameCodeDLL;
  168.     game_update_and_render *UpdateAndRender;
  169.     game_get_sound_samples *GetSoundSamples;
  170.     bool isValid;
  171. };
  172.  
  173. // The platform layer is ca
  174. internal win32_game_code Win32LoadGameCode(void)
  175. {
  176.     win32_game_code Result = {};
  177.  
  178.     // Attempt to load our library.
  179.     Result.GameCodeDLL = LoadLibraryA("handmade.exe");
  180.  
  181.     // If we load the library successfully, get the functions we need and assign them to the stub functions created above.
  182.     if (Result.GameCodeDLL) {
  183.  
  184.         // Gets the address of a process. Since it has no knowledge of the return type we want, we need to cast.
  185.         Result.UpdateAndRender = (game_update_and_render *)GetProcAddress(Result.GameCodeDLL, "GameUpdateAndRender");
  186.         Result.GetSoundSamples = (game_get_sound_samples *)GetProcAddress(Result.GameCodeDLL, "GameGetSoundSamples");
  187.         Result.isValid = (Result.UpdateAndRender && Result.GetSoundSamples);
  188.     }
  189.  
  190.     // If we didn't get the address of our processes, set them to the empty stub functions.
  191.     if (!Result.isValid) {
  192.         Result.UpdateAndRender = GameUpdateAndRenderStub;
  193.         Result.GetSoundSamples = GameGetSoundSamplesStub;
  194.     }
  195.  
  196.     return Result;
  197. }
  198.  
  199. // Attempts to load a DLL needed to provide the underlying functionality for the XInput methods we use.
  200. internal void Win32LoadXInput(void)
  201. {
  202.     // TODO: Test on Windows 8!
  203.     HMODULE XInputLibrary = LoadLibraryA("xinput1_4.dll");
  204.  
  205.     if (!XInputLibrary)
  206.         LoadLibraryA("xinput1_3.dll");
  207.     if (!XInputLibrary)
  208.         LoadLibraryA("xinput9_1_0.dll");
  209.  
  210.     // If we load the library successfully, get the functions we need and assign them to the stub functions created above.
  211.     if (XInputLibrary) {
  212.  
  213.         // Gets the address of a process. Since it has no knowledge of the return type we want, we need to cast.
  214.         XInputGetState = (x_input_get_state *)GetProcAddress(XInputLibrary, "XInputGetState");
  215.         if (!XInputGetState) { XInputGetState = XInputGetStateStub; }
  216.         XInputSetState = (x_input_set_state *)GetProcAddress(XInputLibrary, "XInputSetState");
  217.         if (!XInputSetState) { XInputSetState = XInputSetStateStub; }
  218.     }
  219. }
  220.  
  221. // Initializes sound for our game, after a LONG series of steps.
  222. internal void Win32InitDSound(HWND Window, int32_t SamplesPerSecond, int32_t BufferSize)
  223. {
  224.     // Load the .dll and associated vtable into memory to enable calls relating to DirectSound such as SetCooperativeLevel().
  225.     HMODULE DSoundLibrary = LoadLibraryA("dsound.dll");
  226.  
  227.     // If we find the library, proceed.
  228.     if (DSoundLibrary) {
  229.  
  230.         // Get a DirectSound object and set the correct mode.
  231.         direct_sound_create *DirectSoundCreate = (direct_sound_create *)GetProcAddress(DSoundLibrary, "DirectSoundCreate");
  232.  
  233.         // If we actually get the process back and we are returned a certain value, continue.
  234.         // Double check if this works on XP. DirectSound8 or DirectSound7?
  235.         LPDIRECTSOUND DirectSound;
  236.         if (DirectSoundCreate && SUCCEEDED(DirectSoundCreate(0, &DirectSound, 0))) {
  237.  
  238.             // Set the format of the primary buffer. Dields in the documentation are actually out of order, rearrange them.
  239.             WAVEFORMATEX WaveFormat = {};
  240.             WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
  241.             WaveFormat.nChannels = 2;
  242.             WaveFormat.nSamplesPerSec = SamplesPerSecond;
  243.             WaveFormat.wBitsPerSample = 16;
  244.             WaveFormat.nBlockAlign = (WaveFormat.nChannels * WaveFormat.wBitsPerSample) / 8;
  245.             WaveFormat.nAvgBytesPerSec = WaveFormat.nSamplesPerSec * WaveFormat.nBlockAlign;
  246.             WaveFormat.cbSize = 0;
  247.  
  248.             // When our window is active, set the cooperative mode so we can can call SetFormat() later on.
  249.             if (SUCCEEDED(DirectSound->SetCooperativeLevel(Window, DSSCL_PRIORITY))) {
  250.  
  251.                 // Set the properties of the primary buffer.
  252.                 DSBUFFERDESC BufferDescription = {};
  253.                 BufferDescription.dwSize = sizeof(BufferDescription);
  254.                 BufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
  255.  
  256.                 // Attempts to create actually create the primary buffer - a handle to the sound card. WE WILL NEVER USE THIS AS A BUFFER!
  257.                 LPDIRECTSOUNDBUFFER PrimaryBuffer;
  258.                 if (SUCCEEDED(DirectSound->CreateSoundBuffer(&BufferDescription, &PrimaryBuffer, 0))) {
  259.  
  260.                     HRESULT Error = PrimaryBuffer->SetFormat(&WaveFormat);
  261.                     // We succeeded in making the sound card play in a format we want.
  262.                     if (SUCCEEDED(Error)) {
  263.                         OutputDebugStringA("Primary buffer format was set.\n");
  264.                     }
  265.  
  266.                     // We couldn't set the properies of the primary buffer. Perform logging here.
  267.                     else {
  268.  
  269.                     }
  270.                 }
  271.  
  272.                 // We didn't create a primary buffer. Perform logging here.
  273.                 else {
  274.  
  275.                 }
  276.  
  277.             }
  278.  
  279.             // Setting the cooperative level was not successful. Perform logging here.
  280.             else {
  281.  
  282.             }
  283.  
  284.             // Investigate DSBCAPS_GETCURRENTPOSITION for accuracy purposes.
  285.             DSBUFFERDESC BufferDescription = {};
  286.             BufferDescription.dwSize = sizeof(BufferDescription);
  287.             BufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
  288.             BufferDescription.dwBufferBytes = BufferSize;
  289.             BufferDescription.lpwfxFormat = &WaveFormat;
  290.  
  291.             // Attempt to create a secondary buffer, in which we can actually write to.
  292.             HRESULT Error = DirectSound->CreateSoundBuffer(&BufferDescription, &GlobalSecondaryBuffer, 0);
  293.             if (SUCCEEDED(Error)) {
  294.                 OutputDebugStringA("Secondary buffer was created.\n");
  295.             }
  296.         }
  297.  
  298.         // If we don't get the process back, break into a diagnostic.
  299.         else {
  300.  
  301.         }
  302.     }
  303.  
  304.     // We could not find the DSoundLibrary. Run the game, but without sound.
  305.     else {
  306.  
  307.     }
  308. }
  309.  
  310. // Returns the size of our window as a struct we define.
  311. internal win32_window_dimension Win32GetWindowDimension(HWND Window)
  312. {
  313.     win32_window_dimension Result;
  314.     RECT ClientRect;
  315.     GetClientRect(Window, &ClientRect);
  316.     Result.width = ClientRect.right - ClientRect.left;
  317.     Result.height = ClientRect.bottom - ClientRect.top;
  318.  
  319.     return Result;
  320. }
  321.  
  322. // Initializes or resizes the bitmap that we write into, that Windows can then display using GDI.
  323. internal void Win32ResizeDeviceIndependentBitmapSection(win32_offscreen_buffer *Buffer, int width, int height)
  324. {
  325.     // If we need to resize our DIB section but the bitmap memory is already allocated, free the bitmap memory.
  326.     // VirtualProtect() could have been used here to "leak" the memory but not release to OS, to assert on access-after-free bugs.
  327.     if (Buffer->Memory)
  328.         VirtualFree(Buffer->Memory, 0, MEM_RELEASE);
  329.  
  330.     // Sets the size of the bitmap, along with the size of a pixel in the bitmap.
  331.     Buffer->width = width;
  332.     Buffer->height = height;
  333.     int bytesPerPixel = 4;
  334.     Buffer->bytesPerPixel = bytesPerPixel;
  335.  
  336.     // Ask Windows to make us a bitmap we can use.
  337.     Buffer->Info.bmiHeader.biSize = sizeof(Buffer->Info.bmiHeader);
  338.     Buffer->Info.bmiHeader.biWidth = Buffer->width;
  339.     // When the biHeight field is negative, Windows treats the bitmap as top-down, not bottom-up.
  340.     Buffer->Info.bmiHeader.biHeight = -Buffer->height;
  341.     Buffer->Info.bmiHeader.biPlanes = 1;
  342.     // Add 8 bits padding to the original 24 bits per pixel RGB for 4-byte memory alignment concerns. Performance!
  343.     Buffer->Info.bmiHeader.biBitCount = 32;
  344.     Buffer->Info.bmiHeader.biCompression = BI_RGB;
  345.  
  346.     // Determine how much memory we need for the bitmap.
  347.     int bitmapMemorySize = (Buffer->width * Buffer->height) * bytesPerPixel;
  348.  
  349.     // Actually allocate the memory we need for our bitmap buffer.
  350.     Buffer->Memory = VirtualAlloc(0, bitmapMemorySize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  351.  
  352.     // Set the pitch, the distance from one row to the next.
  353.     Buffer->pitch = Buffer->width * bytesPerPixel;
  354. }
  355.  
  356. // Updates the window to account for it becoming dirty, obscured, or moving off screen.
  357. internal void Win32DisplayBufferInWindow(win32_offscreen_buffer *Buffer, HDC DeviceContext, int windowWidth, int windowHeight)
  358. {
  359.     // Actually blit it to the screen. In effect, takes a rectangle of a size and copies it to another, accounting for scaling.
  360.     // TODO: Need to correct for aspect ratio! Play with stretch modes?
  361.     StretchDIBits(DeviceContext, 0, 0, windowWidth, windowHeight, 0, 0, Buffer->width, Buffer->height, Buffer->Memory, &Buffer->Info, DIB_RGB_COLORS, SRCCOPY);
  362. }
  363.  
  364. // Anytime Windows needs something done on a window, it calls us and we need to respond.
  365. LRESULT CALLBACK Win32MainWindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam)
  366. {
  367.     // Initially, assume we handled the message.
  368.     LRESULT result = 0;
  369.  
  370.     // Actually handle the message, which is yet to be implemented.
  371.     switch (Message) {
  372.         case WM_SIZE:
  373.         {
  374.             OutputDebugStringA("WM_SIZE\n");
  375.             break;
  376.         }
  377.         case WM_CLOSE:
  378.         {
  379.             globalRunning = false;
  380.             break;
  381.         }
  382.         case WM_ACTIVATEAPP:
  383.         {
  384.             OutputDebugStringA("WM_ACTIVATEAPP\n");
  385.             break;
  386.         }
  387.         case WM_DESTROY:
  388.         {
  389.             globalRunning = false;
  390.             break;
  391.         }
  392.         case WM_SYSKEYDOWN:
  393.         case WM_SYSKEYUP:
  394.         case WM_KEYDOWN:
  395.         case WM_KEYUP:
  396.         {
  397.             Assert("Came in through somewhere else, from a non-dispatch message.");
  398.         } break;
  399.         case WM_PAINT:
  400.         {
  401.             // BeginPaint is needed for validating a dirty region of the window - such as when it's resized.
  402.             PAINTSTRUCT Paint;
  403.             HDC DeviceContext = BeginPaint(Window, &Paint);
  404.  
  405.             // Since we assume we need to refresh at 30+ FPS, there's no need to calculate the specific bounds of the window to be repainted.
  406.             win32_window_dimension Dimension = Win32GetWindowDimension(Window);
  407.  
  408.            // Pass the bounded values to the update function.
  409.             Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, Dimension.width, Dimension.height);
  410.  
  411.             // Signal to Windows that the repaint was actually done, so it can stop asking us to repaint it.
  412.             EndPaint(Window, &Paint);
  413.             break;
  414.         }
  415.         default:
  416.         {
  417.             // OutputDebugStringA("default\n");
  418.             result = DefWindowProcA(Window, Message, WParam, LParam);
  419.             break;
  420.         }
  421.     }
  422.  
  423.     return result;
  424. }
  425.  
  426. internal void Win32ClearBuffer(win32_sound_output *SoundOutput)
  427. {
  428.     // Determine the respective sizes of the sound buffer partitions and create handles to refer to them.
  429.     VOID *Region1;
  430.     DWORD Region1Size;
  431.     VOID *Region2;
  432.     DWORD Region2Size;
  433.  
  434.     // Lock the entire sound buffer...
  435.     if (SUCCEEDED(GlobalSecondaryBuffer->Lock(0, SoundOutput->secondaryBufferSize, &Region1, &Region1Size, &Region2, &Region2Size, 0))) {
  436.  
  437.         // and overwrite it with zeroes.
  438.         uint8_t *destinationSample = (uint8_t *)Region1;
  439.         for (DWORD byteIndex = 0; byteIndex < Region1Size; ++byteIndex)
  440.             *destinationSample++ = 0;
  441.  
  442.         // Just in case something wonky happens, also clear the other part of our buffer. Shouldn't happen, due to us locking the entire buffer above.
  443.         destinationSample = (uint8_t *)Region2;
  444.         for (DWORD byteIndex = 0; byteIndex < Region2Size; ++byteIndex)
  445.             *destinationSample++ = 0;
  446.  
  447.         // Unlock the sound buffer for writing.
  448.         GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size);
  449.     }
  450. }
  451.  
  452. internal void Win32FillSoundBuffer(win32_sound_output *SoundOutput, DWORD BytesToLock, DWORD BytesToWrite, game_sound_output_buffer *SourceBuffer)
  453. {
  454.     // Determine the respective sizes of the sound buffer partitions and create handles to refer to them.
  455.     VOID *Region1;
  456.     DWORD Region1Size;
  457.     VOID *Region2;
  458.     DWORD Region2Size;
  459.  
  460.     // Lock to write to the sound buffer containing samples [int16_t int16_t] at wherever our sample index is.
  461.     if (SUCCEEDED(GlobalSecondaryBuffer->Lock(BytesToLock, BytesToWrite, &Region1, &Region1Size, &Region2, &Region2Size, 0))) {
  462.  
  463.         // Determine the number of samples to iterate over, depending on the respective buffer partitions.
  464.         DWORD Region1SampleCount = Region1Size / SoundOutput->bytesPerSample;
  465.  
  466.         int16_t *sourceSample = SourceBuffer->Samples;
  467.         int16_t *destinationSample = (int16_t *)Region1;
  468.  
  469.         for (DWORD sampleIndex = 0; sampleIndex < Region1SampleCount; ++sampleIndex) {
  470.  
  471.             // Copy the samples from our source buffer to our destination buffer.
  472.             *destinationSample++ = *sourceSample++;
  473.             *destinationSample++ = *sourceSample++;
  474.             ++SoundOutput->runningSampleIndex;
  475.         }
  476.  
  477.         DWORD Region2SampleCount = Region2Size / SoundOutput->bytesPerSample;
  478.         destinationSample = (int16_t *)Region2;
  479.  
  480.         for (DWORD sampleIndex = 0; sampleIndex < Region2SampleCount; ++sampleIndex) {
  481.  
  482.             // Copy the samples from our source buffer to our destination buffer.
  483.             *destinationSample++ = *sourceSample++;
  484.             *destinationSample++ = *sourceSample++;
  485.             ++SoundOutput->runningSampleIndex;
  486.         }
  487.  
  488.         // Unlock the sound buffer for writing.
  489.         GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size);
  490.     }
  491. }
  492.  
  493. internal void Win32ProcessKeyboardMessage(game_button_state *NewState, bool isDown)
  494. {
  495.     // Check that we only enter this method when the pressed state of a key changes.
  496.     Assert(NewState->endedDown != isDown);
  497.    
  498.     // The state of the buttons pressed down at the time of polling.
  499.     NewState->endedDown = isDown;
  500.  
  501.     // If the button ended down, there was a half transition before. Update and increment the total number of half transition counts.
  502.     ++NewState->halfTransitionCount;
  503. }
  504.  
  505. internal void Win32ProcessXInputDigitalButton(DWORD XInputButtonState, game_button_state *OldState, DWORD ButtonBit, game_button_state *NewState)
  506. {
  507.     // The state of the buttons pressed down at the time of polling.
  508.     NewState->endedDown = ((XInputButtonState & ButtonBit) == ButtonBit);
  509.  
  510.     // If the button ended down, there was a half transition before. Otherwise, there was no half transition.
  511.     NewState->halfTransitionCount = (OldState->endedDown != NewState->endedDown) ? 1 : 0;
  512. }
  513.  
  514. internal float Win32ProcessXInputStickValue(SHORT value, SHORT deadzoneThreshold)
  515. {
  516.     float result = 0;
  517.  
  518.     /** Implements a smooth progression at the border of deadzone, instead of a jump to 0.2. */
  519.     if (value <= -deadzoneThreshold)
  520.         result = (float)((value + deadzoneThreshold) / (32768.0f - deadzoneThreshold));
  521.     else if (value > deadzoneThreshold)
  522.         result = (float)((value - deadzoneThreshold) / (32767.0f - deadzoneThreshold));
  523.  
  524.     return result;
  525. }
  526.  
  527. internal void Win32ProcessPendingMessages(game_controller_input *KeyboardController)
  528. {                      
  529.     // If our message is a keyboard message, don't ask Windows to translate the message!
  530.     // PeekMessage() allows us to keep globalRunning when there are no messages to process, instead of blocking. PM_REMOVE removes messages as they are processed.
  531.     MSG Message;
  532.     while (PeekMessage(&Message, 0, 0, 0, PM_REMOVE)) {
  533.  
  534.         switch (Message.message) {
  535.             // Stop the game if we get a quit message from Windows.
  536.             case WM_QUIT:
  537.             {
  538.                 globalRunning = false;
  539.             } break;
  540.             case WM_SYSKEYDOWN:
  541.             case WM_SYSKEYUP:
  542.             case WM_KEYDOWN:
  543.             case WM_KEYUP:
  544.             {
  545.                 // A VKCode is a representation of a keyboard button with no ANSI equialent. Windows feeds sends us these via WParam.
  546.                 // Skip dispatching the messages from Windows, and get the message directly from the MSG structure.
  547.                 uint32_t VKCode = (uint32_t)Message.wParam;
  548.  
  549.                 // lParam is a bitfield. The 30th bit indicates if the key was previously held down.
  550.                 bool wasDown = ((Message.lParam & (1 << 30)) != 0);
  551.                 bool isDown = ((Message.lParam & (1 << 31)) == 0);
  552.                 if (wasDown != isDown) {
  553.                     if (VKCode == 'W') {
  554.                         Win32ProcessKeyboardMessage(&KeyboardController->moveUp, isDown);
  555.                     }
  556.                     else if (VKCode == 'A') {
  557.                         Win32ProcessKeyboardMessage(&KeyboardController->moveLeft, isDown);
  558.                     }
  559.                     else if (VKCode == 'S') {
  560.                         Win32ProcessKeyboardMessage(&KeyboardController->moveRight, isDown);
  561.                     }
  562.                     else if (VKCode == 'D') {
  563.                         Win32ProcessKeyboardMessage(&KeyboardController->moveDown, isDown);
  564.                     }
  565.                     else if (VKCode == 'Q') {
  566.                         Win32ProcessKeyboardMessage(&KeyboardController->leftShoulder, isDown);
  567.                     }
  568.                     else if (VKCode == 'E') {
  569.                         Win32ProcessKeyboardMessage(&KeyboardController->rightShoulder, isDown);
  570.                     }
  571.                     else if (VKCode == VK_UP) {
  572.                         Win32ProcessKeyboardMessage(&KeyboardController->actionUp, isDown);
  573.                     }
  574.                     else if (VKCode == VK_DOWN) {
  575.                         Win32ProcessKeyboardMessage(&KeyboardController->actionDown, isDown);
  576.                     }
  577.                     else if (VKCode == VK_LEFT) {
  578.                         Win32ProcessKeyboardMessage(&KeyboardController->actionLeft, isDown);
  579.                     }
  580.                     else if (VKCode == VK_RIGHT) {
  581.                         Win32ProcessKeyboardMessage(&KeyboardController->actionRight, isDown);
  582.                     }
  583.                     else if (VKCode == VK_ESCAPE) {
  584.                         Win32ProcessKeyboardMessage(&KeyboardController->start, isDown);             
  585.                     }
  586.                     else if (VKCode == VK_SPACE) {
  587.                         Win32ProcessKeyboardMessage(&KeyboardController->back, isDown);
  588.                     }
  589. #if HANDMADE_INTERNAL
  590.                     else if (VKCode == 'P') {
  591.                         if (isDown) {
  592.                             globalPause = !globalPause;
  593.                         }
  594.                     }
  595. #endif
  596.                 }
  597.                            
  598.                 // Determine if the [Alt] key was held down. If so, quit he game if [F4] is also pressed.
  599.                 int32_t altKeyWasDown = (Message.lParam & (1 << 29));
  600.                 if ((VKCode == VK_F4) && altKeyWasDown) {
  601.                     globalRunning = false;
  602.                 }
  603.             } break;
  604.  
  605.             default:
  606.             {  
  607.                 // Translate and dispatch the message. Turns keyboard messages into more "proper" keyboard messages.
  608.                 TranslateMessage(&Message);
  609.                 DispatchMessage(&Message);
  610.             }
  611.         }
  612.     }
  613. }
  614.  
  615. LARGE_INTEGER Win32GetWallClock(void)
  616. {
  617.     // Finish measuring wall clock time and cycle count.
  618.     LARGE_INTEGER result;
  619.     QueryPerformanceCounter(&result);
  620.     return result;
  621. }
  622.  
  623. inline float Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End)
  624. {
  625.     // How much time has actually elapsed so far?
  626.     float result = ((float)(End.QuadPart - Start.QuadPart) / (float)globalPerformanceCounterFrequency);
  627.     return result;
  628. }
  629.  
  630. internal void Win32DebugDrawVertical(win32_offscreen_buffer *Backbuffer, int x, int top, int bottom, uint32_t color)
  631. {
  632.     // Check that we don't draw above the top or below the bottom. If we do, clip.
  633.     if (top <= 0)
  634.         top = 0;
  635.     if (bottom >= Backbuffer->height)
  636.         bottom = Backbuffer->height;
  637.  
  638.     // Check that we don't draw past our buffer. If we don't, actually draw.
  639.     if ((x >= 0) && (x < Backbuffer->width)) {
  640.         uint8_t *Pixel = ((uint8_t *)Backbuffer->Memory + x * Backbuffer->bytesPerPixel + top * Backbuffer->pitch);
  641.         for (int y = top; y < bottom; ++y) {
  642.             *(uint32_t *)Pixel = color;
  643.             Pixel += Backbuffer->pitch;
  644.         }
  645.     }
  646. }
  647.  
  648. /** Actually draw where our desired frame flips are relative to where our audio is playing. */
  649. inline void Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer, win32_sound_output *SoundOutput, float scalingFactor, int padX, int top, int bottom, DWORD Value, uint32_t Color)
  650. {
  651.     float xfloat32 = (scalingFactor * (float)Value);
  652.     int x = padX + (int)xfloat32;
  653.     Win32DebugDrawVertical(Backbuffer, x, top, bottom, Color);
  654. }
  655.  
  656. /** Draw where our desired frame flips are relative to where our audio is playing. */
  657. internal void Win32DebugSyncDisplay(win32_offscreen_buffer *Backbuffer, int MarkerCount, win32_debug_time_marker *Markers, int currentMarkerIndex, win32_sound_output *SoundOutput, float targetSecondsPerFrame)
  658. {
  659.     // We need to scale our drawing rectangle from bytes to pixels. Pad this value, so the whole area can be seen on screen.
  660.     int padX = 16;
  661.     int padY = 16;
  662.     int lineHeight = 64;
  663.     float scalingFactor = (float)(Backbuffer->width - 2 * padX) / (float)(SoundOutput->secondaryBufferSize);
  664.    
  665.     for (int markerIndex = 0; markerIndex < MarkerCount; ++markerIndex) {
  666.  
  667.         win32_debug_time_marker *ThisMarker = &Markers[markerIndex];
  668.         Assert(ThisMarker->OutputPlayCursor < SoundOutput->secondaryBufferSize);
  669.         Assert(ThisMarker->OutputWriteCursor < SoundOutput->secondaryBufferSize);
  670.         Assert(ThisMarker->OutputLocation < SoundOutput->secondaryBufferSize);
  671.         Assert(ThisMarker->OutputByteCount < SoundOutput->secondaryBufferSize);
  672.         Assert(ThisMarker->FlipPlayCursor < SoundOutput->secondaryBufferSize);
  673.         Assert(ThisMarker->FlipWriteCursor < SoundOutput->secondaryBufferSize);
  674.  
  675.         // If the marker is actually being written, mark it as current and draw it using a highlighted color. Else, it's draw with no highlight.
  676.         DWORD PlayColor = 0xFFFFFFFF;
  677.         DWORD WriteColor = 0xFFFF0000;
  678.         DWORD ExpectedFlipColor = 0xFFFFFF00;
  679.         DWORD PlayWindowColor = 0xFFFF00FF;
  680.        
  681.         // Controls the pading for how the lines are drawn.
  682.         int top = padY;
  683.         int bottom = padY + lineHeight;
  684.  
  685.         if (markerIndex == currentMarkerIndex) {
  686.             top += lineHeight + padY;
  687.             bottom += lineHeight + padY;
  688.  
  689.             int firstTop = top;
  690.  
  691.             Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputPlayCursor, PlayColor);
  692.             Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputWriteCursor, WriteColor);
  693.  
  694.             top += lineHeight + padY;
  695.             bottom += lineHeight + padY;
  696.  
  697.             Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputLocation, PlayColor);
  698.             Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputLocation + ThisMarker->OutputByteCount, WriteColor);
  699.  
  700.             top += lineHeight + padY;
  701.             bottom += lineHeight + padY;
  702.  
  703.             Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, firstTop, bottom, ThisMarker->ExpectedFlipPlayCursor, ExpectedFlipColor);
  704.         }
  705.  
  706.         Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->FlipPlayCursor, PlayColor);
  707.         Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->FlipPlayCursor + 480 * SoundOutput->bytesPerSample, PlayWindowColor);
  708.         Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->FlipWriteCursor, WriteColor);
  709.     }
  710. }
  711.  
  712. // Insert the entry point for Windows, importantly where the handle to our executable is.
  713. int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  714. {
  715.     // Attempt to load the DLL that contains our game-specific functions.
  716.     win32_game_code Game = Win32LoadGameCode();
  717.  
  718.     // Determine how many clock cycles the CPU goes through in a single second.
  719.     LARGE_INTEGER PerformanceCounterFrequencyResult;
  720.     QueryPerformanceFrequency(&PerformanceCounterFrequencyResult);
  721.     int64_t globalPerformanceCounterFrequency = PerformanceCounterFrequencyResult.QuadPart;
  722.    
  723.     // Set the scheduler polling period to 1 msec, so sleep doesn't block excessively. Check to see if it was able to be set.
  724.     UINT desiredSchedulerMS = 1;
  725.     bool sleepIsNonBlocking = (timeBeginPeriod(desiredSchedulerMS) == TIMERR_NOERROR);
  726.  
  727.     // Controller and window initialization.
  728.     Win32LoadXInput();
  729.     WNDCLASS WindowClass = {};
  730.     Win32ResizeDeviceIndependentBitmapSection(&GlobalBackbuffer, 1280, 720);
  731.     WindowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  732.     WindowClass.lpfnWndProc = Win32MainWindowCallback;
  733.     WindowClass.hInstance = hInstance;
  734.     WindowClass.lpszClassName = "HandmadeHeroWindowClass";
  735.    
  736.     // Todo: How do we query this on Windows?
  737. #define monitorRefreshHz 60
  738. #define gameUpdateHz (monitorRefreshHz / 2)
  739.     float targetSecondsPerFrame = 1.0f / (float)gameUpdateHz;
  740.  
  741.     // Register the window class so that we can open a window. This can fail, so check if it succeds.
  742.     if (RegisterClass(&WindowClass)) {
  743.  
  744.         // Create the window, using the handle that we specify.
  745.         HWND Window = CreateWindowEx(0, WindowClass.lpszClassName, "Handmade Hero", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0);
  746.  
  747.         // Checks to see if the window was created successfully.
  748.         if (Window) {
  749.  
  750.             // Since we specified CS_OWNDC, we get one device context and use it exclusively.
  751.             HDC DeviceContext = GetDC(Window);
  752.  
  753.             // Graphics test.
  754.             int xOffset = 0;
  755.             int yOffset = 0;
  756.  
  757.             // Initialize sound.
  758.             win32_sound_output SoundOutput = {};
  759.             SoundOutput.samplesPerSecond = 48000;
  760.             SoundOutput.bytesPerSample = (sizeof(int16_t) * 2);
  761.             SoundOutput.secondaryBufferSize = SoundOutput.samplesPerSecond * SoundOutput.bytesPerSample;
  762.             // Todo: Get rid of latencySampleCount.
  763.             SoundOutput.latencySampleCount = 3 * (SoundOutput.samplesPerSecond / gameUpdateHz);
  764.             // Todo: Actually compute this value and see what the lowest bound is.
  765.             SoundOutput.safetyBytes = (SoundOutput.samplesPerSecond * SoundOutput.bytesPerSample / gameUpdateHz) / 3;
  766.             Win32InitDSound(Window, SoundOutput.samplesPerSecond, SoundOutput.secondaryBufferSize);
  767.             Win32ClearBuffer(&SoundOutput);
  768.             GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
  769.  
  770.             globalRunning = true;
  771. #if 0
  772.             // Testing: Locks the program so we can observe the sound card write granularity.
  773.             while (globalRunning) {
  774.                 DWORD PlayCursor;
  775.                 DWORD WriteCursor;
  776.                 GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor);
  777.                 char TextBuffer[256];
  778.                 _snprintf_s(TextBuffer, sizeof(TextBuffer), "PC:%u WC:%u \n", PlayCursor, WriteCursor);
  779.                 OutputDebugStringA(TextBuffer);
  780.             }
  781. #endif
  782.  
  783.             // On the stack, reserve memory to hold the samples used in our sound buffer.
  784.             // Todo: Pool this memory with the bitmap VirtualAlloc() into a seperate call.
  785.             int16_t *Samples = (int16_t *)VirtualAlloc(0, SoundOutput.secondaryBufferSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  786.  
  787. // For our testing, mandate a mixed location in memory to hold our game memory.This results in deteminsitic pointer locations for a same run of the game.
  788. #if HANDMADE_INTERNAL
  789.             LPVOID BaseAddress = (LPVOID)terabytes((uint64_t)2);
  790. #else
  791.             LPVOID BaseAddress = 0;
  792. #endif
  793.             // Declare a buffer to hold our game memory and allocate memory to our permanent storage and transient storage partitions.
  794.             // Dev: On 32-bit systems, need to account for integral promotion, hence the (uint64_t) cast in 450.
  795.             game_memory GameMemory = {};
  796.             GameMemory.permanantStorageSize = megabytes(64);
  797.             GameMemory.transientStorageSize = gigabytes(1);
  798.             GameMemory.DEBUGPlatformFreeFileMemory = DEBUGPlatformFreeFileMemory;
  799.             GameMemory.DEBUGPlatformReadEntireFile = DEBUGPlatformReadEntireFile;
  800.             GameMemory.DEBUGPlatformWriteEntireFile = DEBUGPlatformWriteEntireFile;
  801.  
  802.             uint64_t totalSize = GameMemory.permanantStorageSize + GameMemory.transientStorageSize;
  803.             GameMemory.permanentStorage = VirtualAlloc(BaseAddress, (size_t)totalSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  804.  
  805.             // Cast to a byte pointer and advance exactly past where the permanent storage size memory partion ends.
  806.             GameMemory.transientStorage = ((uint8_t *)GameMemory.permanentStorage + GameMemory.permanantStorageSize);
  807.  
  808.             // Check to see if the sound and game memory have been allocated correctly.
  809.             if (Samples && GameMemory.permanentStorage && GameMemory.transientStorage) {
  810.  
  811.                 // Declare two buffers to store our old input and new input state, clear initially.
  812.                 game_input Input[2] = {};
  813.                 game_input *NewInput = &Input[0];
  814.                 game_input *OldInput = &Input[1];
  815.                
  816.                 // Start measuring wall clock time. Generate the processor cycle count since the last reset using __rdtsc.
  817.                 LARGE_INTEGER LastCounter = Win32GetWallClock();
  818.                 LARGE_INTEGER flipWallClock = Win32GetWallClock();
  819.                
  820.                 int DebugTimeMarkerIndex = 0;
  821.                 win32_debug_time_marker DebugTimeMarkers[gameUpdateHz / 2] = {0};
  822.                
  823.                 DWORD audioLatencyInBytes = 0;
  824.                 float audioLatencyInSeconds = 0;
  825.                 bool soundIsValid = false;
  826.  
  827.                 uint64_t lastCycleCount = __rdtsc();
  828.  
  829.                 // Loop through the message queue and send them to the window.
  830.                 while (globalRunning) {
  831.                    
  832.                     // Let's have the keyboard come in as the first controller.
  833.                     // Todo: Make a zeroing macro at some point, otherise the previous keyup and keydown state will be wrong.
  834.                     game_controller_input *OldKeyboardController = GetController(OldInput, 0);
  835.                     game_controller_input *NewKeyboardController = GetController(NewInput, 0);
  836.                     *NewKeyboardController = {};
  837.                     NewKeyboardController->isConnected = true;
  838.  
  839.                     // Maps the up/down state of the keyboard from the previous frame to the current frame, to not lose state.
  840.                     for (int buttonIndex = 0; buttonIndex < elementsInArray(NewKeyboardController->Buttons); ++buttonIndex) {
  841.                         NewKeyboardController->Buttons[buttonIndex].endedDown = OldKeyboardController->Buttons[buttonIndex].endedDown;
  842.                     }
  843.  
  844.                     Win32ProcessPendingMessages(NewKeyboardController);
  845.  
  846.                     // Don't run if we're paused.
  847.                     if (!globalPause) {
  848.  
  849.                         // Todo: Don't poll disconnected controllers to avoid XInput frame rate hit on older libraries.
  850.                         DWORD maxControllerCount = XUSER_MAX_COUNT;
  851.                         if (maxControllerCount > (elementsInArray(NewInput->Controllers) - 1)) {
  852.                             maxControllerCount = (elementsInArray(NewInput->Controllers) - 1);
  853.                         }
  854.  
  855.                         // Loop over all the XInput controllers and poll them.
  856.                         for (DWORD ControllerIndex = 0; ControllerIndex < maxControllerCount; ++ControllerIndex) {
  857.  
  858.                             // Write the state of the controller's previous input, and the current input to be processed.
  859.                             int ourControllerIndex = ControllerIndex + 1;
  860.                             game_controller_input *OldController = GetController(OldInput, ourControllerIndex);
  861.                             game_controller_input *NewController = GetController(NewInput, ourControllerIndex);
  862.  
  863.                             // Holds the state of the controller we specify.
  864.                             XINPUT_STATE ControllerState;
  865.  
  866.                             // The controller is plugged in. See if dwPacketumber increments too rapidly, meaning we need to poll more often.
  867.                             if (XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS) {
  868.  
  869.                                 NewController->isConnected = true;
  870.  
  871.                                 // Defines a simple way to refer to the controller.
  872.                                 XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad;
  873.  
  874.                                 // Todo: this is a square deadzone, check XInput to check if the deadzone is circular. Implement if possible.
  875.                                 NewController->stickAverageX = Win32ProcessXInputStickValue(Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
  876.                                 NewController->stickAverageY = Win32ProcessXInputStickValue(Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
  877.  
  878.                                 if ((NewController->stickAverageX != 0.0f) || (NewController->stickAverageY != 0.0f))
  879.                                     NewController->isAnalog = true;
  880.  
  881.                                 // Accounts for d-pad handling.        
  882.                                 if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_UP) {
  883.                                     NewController->stickAverageY = 1.0f;
  884.                                     NewController->isAnalog = false;
  885.                                 }
  886.                                 if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {
  887.                                     NewController->stickAverageY = -1.0f;
  888.                                     NewController->isAnalog = false;
  889.                                 }
  890.                                 if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {
  891.                                     NewController->stickAverageX = -1.0f;
  892.                                     NewController->isAnalog = false;
  893.                                 }
  894.                                 if (Pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {
  895.                                     NewController->stickAverageX = 1.0f;
  896.                                     NewController->isAnalog = false;
  897.                                 }
  898.  
  899.                                 // Decrypts what button on the controller is being pressed.            
  900.                                 float threshold = 0.5f;
  901.                                 Win32ProcessXInputDigitalButton(NewController->stickAverageX <= -threshold ? 1 : 0, &OldController->moveLeft, 1, &NewController->moveLeft);
  902.                                 Win32ProcessXInputDigitalButton(NewController->stickAverageX > threshold ? 1 : 0, &OldController->moveRight, 1, &NewController->moveRight);
  903.                                 Win32ProcessXInputDigitalButton(NewController->stickAverageY <= -threshold ? 1 : 0, &OldController->moveDown, 1, &NewController->moveDown);
  904.                                 Win32ProcessXInputDigitalButton(NewController->stickAverageY > threshold ? 1 : 0, &OldController->moveUp, 1, &NewController->moveUp);
  905.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->actionDown, XINPUT_GAMEPAD_A, &NewController->actionDown);
  906.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->actionRight, XINPUT_GAMEPAD_B, &NewController->actionRight);
  907.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->actionLeft, XINPUT_GAMEPAD_X, &NewController->actionLeft);
  908.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->actionUp, XINPUT_GAMEPAD_Y, &NewController->actionUp);
  909.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->leftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER, &NewController->leftShoulder);
  910.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->rightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER, &NewController->rightShoulder);
  911.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->start, XINPUT_GAMEPAD_START, &NewController->start);
  912.                                 Win32ProcessXInputDigitalButton(Pad->wButtons, &OldController->back, XINPUT_GAMEPAD_BACK, &NewController->back);
  913.                             }
  914.                        
  915.                         // The controller is not available. It is not always a error.
  916.                         else {
  917.                             NewController->isConnected = false;
  918.                         }
  919.                     }
  920.  
  921.                     // Update the game loop.
  922.                     game_offscreen_buffer Buffer = {};
  923.                     Buffer.Memory = GlobalBackbuffer.Memory;
  924.                     Buffer.width = GlobalBackbuffer.width;
  925.                     Buffer.height = GlobalBackbuffer.height;
  926.                     Buffer.pitch = GlobalBackbuffer.pitch;
  927.                     Game.UpdateAndRender(&GameMemory, NewInput, &Buffer);
  928.  
  929.                     LARGE_INTEGER audioWallClock = Win32GetWallClock();
  930.                     float fromFrameBeginToAudioInSeconds = Win32GetSecondsElapsed(flipWallClock, audioWallClock);
  931.                    
  932.                     DWORD PlayCursor;
  933.                     DWORD WriteCursor;
  934.  
  935.                     if (GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) {
  936.  
  937.                         // If the sound buffer is valid, start writing at the write cursor, we otherwise start writing where we left off.
  938.                         if (!soundIsValid) {
  939.                             SoundOutput.runningSampleIndex = WriteCursor / SoundOutput.bytesPerSample;
  940.                             soundIsValid = true;
  941.                         }
  942.  
  943.                         // Computes how much space in the sound buffer to reserve. The location of this locked space is were we last wrote tp (the write cursor if the first time through).
  944.                         DWORD BytesToLock = ((SoundOutput.runningSampleIndex * SoundOutput.bytesPerSample) % SoundOutput.secondaryBufferSize);
  945.                         DWORD expectedSoundBytesPerFrame = (SoundOutput.samplesPerSecond * SoundOutput.bytesPerSample) / gameUpdateHz;
  946.                         float secondsLeftUntilFlip = targetSecondsPerFrame - fromFrameBeginToAudioInSeconds;
  947.                         DWORD expectedBytesUntilFlip = ((DWORD)(secondsLeftUntilFlip - fromFrameBeginToAudioInSeconds) * (float)expectedSoundBytesPerFrame);
  948.                         DWORD expectedFrameBoundaryByte = PlayCursor + expectedSoundBytesPerFrame;
  949.  
  950.                         // Assume that our audio card is latent. It is latent if our safety margin falls after a frame boundary flip.
  951.                         DWORD SafeWriteCursor = WriteCursor;
  952.  
  953.                         // Normalize the our safe write cursor to always be ahead of the write cursor.
  954.                         if (SafeWriteCursor < PlayCursor)
  955.                             SafeWriteCursor += SoundOutput.secondaryBufferSize;
  956.                         Assert(SafeWriteCursor >= PlayCursor);
  957.  
  958.                         // If our safety margin determines us to be over a frame boundary, the audio card is latent.
  959.                         SafeWriteCursor = SafeWriteCursor + SoundOutput.safetyBytes;
  960.                         bool audioCardIsLowLatency = (SafeWriteCursor < expectedFrameBoundaryByte);
  961.  
  962.                         /* If the write cursor is after the frame boundary, assume we can NEVER sync audio perfectly and write one frame's worth of audio plus some the
  963.                            number of samples determined by the safety margin. This is in the case of a latent audio card.
  964.                         */
  965.                         DWORD TargetCursor = 0;
  966.                         if (audioCardIsLowLatency) {
  967.                             TargetCursor = (expectedFrameBoundaryByte + expectedSoundBytesPerFrame);
  968.                         }
  969.  
  970.                         /* When we wake up to write audio, look to see what the play cursor position is and forecast where the play cursor will be on the next frame boundary.
  971.                            look to see if the write cursor is before the forecast position. If it is, and before a predetermined safety margin
  972.                            (the number of samples we estimate our game update loop to vary by - probably +2ms), write up to the next frame boundary from the write cursor, and
  973.                             one frame further - gives perfrect audio sync. This is in the case of a non-latent audio card.
  974.                         */
  975.                         else {
  976.                             TargetCursor = WriteCursor + expectedSoundBytesPerFrame + SoundOutput.safetyBytes;
  977.                         }
  978.  
  979.                         // Make sure we always map to within the boundaries of the sound buffer.
  980.                         TargetCursor = TargetCursor % SoundOutput.secondaryBufferSize;
  981.  
  982.                         DWORD BytesToWrite = 0;
  983.                         if (BytesToLock > TargetCursor) {
  984.                             // BytesToLock is ahead of the play cursor, we ran into the dual partition situation. Write to the end of the sound buffer AND to the beginning of the play cursor.
  985.                             BytesToWrite = (SoundOutput.secondaryBufferSize - BytesToLock) + TargetCursor;
  986.                         }
  987.                         else {
  988.                             // BytesToLock is behind the play cursor, we have a single partition sitation. Write up to the beginning of the play cursor, trailing it.
  989.                             BytesToWrite = TargetCursor - BytesToLock;
  990.                         }
  991.  
  992.                         // Initialize the sound buffer and write the appropriate number of sound samples to the appopriate place in the sound buffer.
  993.                         game_sound_output_buffer SoundBuffer = {};
  994.                         SoundBuffer.samplesPerSecond = SoundOutput.samplesPerSecond;
  995.                         SoundBuffer.sampleCount = BytesToWrite / SoundOutput.bytesPerSample;
  996.                         SoundBuffer.Samples = Samples;
  997.                         Game.GetSoundSamples(&GameMemory, &SoundBuffer);
  998.  
  999. #if HANDMADE_INTERNAL
  1000.                         // Update and write information regarding debug time markers.
  1001.                         win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex];
  1002.                         Marker->OutputPlayCursor = PlayCursor;
  1003.                         Marker->OutputWriteCursor = WriteCursor;
  1004.                         Marker->OutputLocation = BytesToLock;
  1005.                         Marker->OutputByteCount = BytesToWrite;
  1006.                         Marker->ExpectedFlipPlayCursor = expectedFrameBoundaryByte;
  1007.  
  1008.                         // When the current write cursor wraps past the buffer, add the total buffer size so it would be in the same place had it not been circular.
  1009.                         DWORD UnwrappedWriteCursor = WriteCursor;
  1010.                         if (UnwrappedWriteCursor < PlayCursor)
  1011.                             UnwrappedWriteCursor += SoundOutput.secondaryBufferSize;
  1012.  
  1013.                         // Compute the latency of the sound card in bytes, using where we play sound and where we write samples.
  1014.                         audioLatencyInBytes = UnwrappedWriteCursor - PlayCursor;
  1015.  
  1016.                         // Using the above, reason about an audio latency in seconds.
  1017.                         audioLatencyInSeconds = (((float)audioLatencyInBytes / (float)SoundOutput.bytesPerSample) / (float)SoundOutput.samplesPerSecond);
  1018.  
  1019.                         // When we filled the sound buffer, where did it think we were?
  1020.                         char TextBuffer[256];
  1021.                         _snprintf_s(TextBuffer, sizeof(TextBuffer), "BTL:%u TC:%u BTW:%u - PC:%u WC:%u\n DELTA:%u (%fs)\n", BytesToLock, TargetCursor, BytesToWrite, PlayCursor, WriteCursor, audioLatencyInBytes, audioLatencyInSeconds);
  1022.                         OutputDebugStringA(TextBuffer);
  1023. #endif
  1024.                         Win32FillSoundBuffer(&SoundOutput, BytesToLock, BytesToWrite, &SoundBuffer);
  1025.                     }
  1026.                     else {
  1027.                         soundIsValid = false;
  1028.                     }
  1029.  
  1030.                     LARGE_INTEGER WorkCounter = Win32GetWallClock();
  1031.                     float workSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter);
  1032.  
  1033.                     // Until we use up all the time we render our frames at, don't flip.
  1034.                     float secondsElapsedForFrame = workSecondsElapsed;
  1035.                     if (secondsElapsedForFrame < targetSecondsPerFrame) {
  1036.  
  1037.                         // Only sleep if we were able to set the scheduler polling period to 1 msec.
  1038.                         if (sleepIsNonBlocking) {
  1039.                             DWORD sleepMS = (DWORD)(1000.0f * (targetSecondsPerFrame - secondsElapsedForFrame));
  1040.                             if (sleepMS > 0)
  1041.                                 Sleep(sleepMS);
  1042.                         }
  1043.  
  1044.                         // Todo: Check that we didn't sleep for too long and miss a frame.
  1045.                         float testSecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock());
  1046.                         if (testSecondsElapsedForFrame < targetSecondsPerFrame) {
  1047.                             // If we did, we need to log a missed sleep.
  1048.                         }
  1049.  
  1050.                         while (secondsElapsedForFrame < targetSecondsPerFrame)
  1051.                             secondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock());
  1052.                     }
  1053.                     else {
  1054.                         // Todo: We missed our frame rate! Need to log this.
  1055.                     }
  1056.  
  1057.                         // After the lock, benchmark the next pass through the loop.
  1058.                         LARGE_INTEGER EndCounter = Win32GetWallClock();
  1059.                         float millisecondsPerFrame = 1000.0f * Win32GetSecondsElapsed(LastCounter, EndCounter);
  1060.                         LastCounter = EndCounter;
  1061.  
  1062.                         // After getting the size of the window, actually blit to our screen.
  1063.                         win32_window_dimension Dimension = Win32GetWindowDimension(Window);
  1064.  
  1065. #if HANDMADE_INTERNAL
  1066.  
  1067.                         // Note: (DebugTimeMarkers - 1), which is the current marker being drawn, is wrong on the 0th index.
  1068.                         Win32DebugSyncDisplay(&GlobalBackbuffer, elementsInArray(DebugTimeMarkers), DebugTimeMarkers, DebugTimeMarkerIndex - 1, &SoundOutput, targetSecondsPerFrame);
  1069.  
  1070. #endif
  1071.                         Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, Dimension.width, Dimension.height);
  1072.  
  1073.                         // Get the time right after the frame flip.
  1074.                         flipWallClock = Win32GetWallClock();
  1075.                        
  1076. #if HANDMADE_INTERNAL  
  1077.                         {
  1078.                             DWORD PlayCursor;
  1079.                             DWORD WriteCursor;
  1080.                             if (GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) {
  1081.                                 Assert(DebugTimeMarkerIndex < elementsInArray(DebugTimeMarkers));
  1082.                                 win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex];
  1083.  
  1084.                                 // Always have where the play and write cursors are when the frame flips.
  1085.                                 Marker->FlipPlayCursor = PlayCursor;
  1086.                                 Marker->FlipWriteCursor = WriteCursor;
  1087.                             }
  1088.                         }
  1089. #endif
  1090.                         // Swap the state of our old and new inputs.
  1091.                         game_input *Temp = NewInput;
  1092.                         NewInput = OldInput;
  1093.                         OldInput = Temp;
  1094.  
  1095.                         // Other logging information.
  1096.                         uint64_t endCycleCount = __rdtsc();
  1097.                         uint64_t cyclesElapsed = endCycleCount - lastCycleCount;
  1098.                         lastCycleCount = endCycleCount;
  1099.                         float framesPerSecond = 0.0f;
  1100.                         float megaCyclesPerFrame = (float)cyclesElapsed / 1000000;
  1101.  
  1102.                         // Display the logging results to the internal console.
  1103.                         char FPSBuffer[256];
  1104.                         _snprintf_s(FPSBuffer, sizeof(FPSBuffer), "ms/frame: %.02fms fps: %.02fFPS: mc/f: %.02fMC/f\n", millisecondsPerFrame, framesPerSecond, megaCyclesPerFrame);
  1105.                         OutputDebugStringA(FPSBuffer);
  1106.  
  1107. #if HANDMADE_INTERNAL
  1108.  
  1109.                         // Increment the index of the debug cursor and reset the index if we exceed our pretermined value - [gameUpdateHz / 2] in this case.
  1110.                         ++DebugTimeMarkerIndex;
  1111.                         if (DebugTimeMarkerIndex == elementsInArray(DebugTimeMarkers))
  1112.                             DebugTimeMarkerIndex = 0;
  1113. #endif
  1114.                     }
  1115.                 }
  1116.             }
  1117.             // Todo: Perform logging for sound and memory and allocation check.
  1118.             else {}
  1119.             }
  1120.         // Todo: Perform logging for Window.
  1121.         else {}
  1122.     }
  1123.     // Todo: Perform logging for registering WindowClass.
  1124.     else {}
  1125.    
  1126.     return 0;
  1127. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement