Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* This is not a final platform layer, not for shipping! Below is a partial list of stuff to implement.
- - Figure out save game locations.
- - Getting a handle to our own executable file.
- - Need asset loading path.
- - Multithreading for multiple cores.
- - Support raw input, for multiple keyboards.
- - Sleep and timeBeginPeriod for laptops.
- - Clip cursor for multi-monitor support.
- - Fullscreen support.
- - WM_SETCURSOR for cursor visibility.
- - QueryCancelAutoplay.
- - WM_ACTIVATEAPP
- - Blit speed improvements to BitBlt.
- - Support hardware acceleration.
- - GetKeyboardLayout for international support.
- */
- #include "handmade.h"
- #include <windows.h>
- #include <stdio.h>
- #include <malloc.h>
- #include <xinput.h>
- #include <dsound.h>
- #include <math.h>
- #include "win32_handmade.h"
- // Here be dragons.
- // DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE* pState) is the function prototype provided in <Xinput.h> for XInputGetState().
- // 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).
- #define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE* pState)
- #define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration)
- #define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter)
- // Now, we need to define a type that are of the function signatures given above.
- // 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.
- // 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).
- typedef X_INPUT_GET_STATE(x_input_get_state);
- typedef X_INPUT_SET_STATE(x_input_set_state);
- typedef DIRECT_SOUND_CREATE(direct_sound_create);
- // 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>.
- X_INPUT_GET_STATE(XInputGetStateStub) {
- return ERROR_DEVICE_NOT_CONNECTED;
- }
- X_INPUT_SET_STATE(XInputSetStateStub) {
- return ERROR_DEVICE_NOT_CONNECTED;
- }
- // Make globals that point to the functions above so that we can actually refer to them.
- // Since we never need to refer to sound once it's initialized, there's no reason to create a global pointer for it.
- global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
- global_variable x_input_set_state *XInputSetState_ = XInputSetStateStub;
- // Any time XInputSetState and XInputGetState are used from now on, they actually refer to the pointers we declared above.
- #define XInputSetState XInputSetState_
- #define XInputGetState XInputGetState_
- // These are globals unrelated to the macro redefinitons above.
- global_variable bool globalRunning;
- global_variable bool globalPause;
- global_variable win32_offscreen_buffer GlobalBackbuffer;
- global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer;
- global_variable int64_t globalPerformanceCounterFrequency;
- // Free the memory occupied by a file.
- DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUGPlatformFreeFileMemory)
- {
- if (Memory)
- VirtualFree(Memory, 0, MEM_RELEASE);
- }
- // Hey, load a file.
- DEBUG_PLATFORM_READ_ENTIRE_FILE(DEBUGPlatformReadEntireFile)
- {
- // Store the result of the call and the size of the file.
- debug_read_file_result Result = {};
- // Get a handle to the file, specify r/w permissons for other processes, and always only open a file that already exists.
- HANDLE FileHandle = CreateFileA(Filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0 , 0);
- if (FileHandle != INVALID_HANDLE_VALUE) {
- // Determine the file size. For a 64 bit, use the modern GetFileSizeEx() which returns a 64-bit value with no alternations.
- LARGE_INTEGER FileSize;
- if (GetFileSizeEx(FileHandle, &FileSize)) {
- // Converts a 64-bit value to a 32-bit value.
- uint32_t FileSize32 = SafeTruncateUInt64(FileSize.QuadPart);
- // Allocate the 64-bit part of the file size.
- Result.Contents = VirtualAlloc(0, FileSize32, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
- // If we got the memory, read into it. Doesn't take a 64-bit value for the bytes to read.
- // Make sure we actually read back the entire size of the file, since another process potentially could modify the size?
- DWORD BytesRead;
- if (ReadFile(FileHandle, Result.Contents, FileSize32, &BytesRead, 0) && (FileSize32 == BytesRead)) {
- Result.contentSize = FileSize32;
- }
- // Else, handle the error case when we couldn't read the file.
- else
- {
- // Free the memory we had immediately, as if we never allocated it.
- DEBUGPlatformFreeFileMemory(Result.Contents);
- Result.Contents = 0;
- }
- }
- // Logging: The file size was not determined.
- else {
- }
- }
- // Loggging: The file handle was not initialized. Error with reading the file.
- else {
- }
- // At the very end of the call, close the file handle.
- CloseHandle(FileHandle);
- return Result;
- }
- DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DEBUGPlatformWriteEntireFile)
- {
- // Initally, the file has not yet been written.
- bool Result = false;
- // Get a handle to the file and always create a new file or overwrite old files.
- HANDLE FileHandle = CreateFileA(Filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
- if (FileHandle != INVALID_HANDLE_VALUE) {
- // If we got the memory, write into it. Doesn't take a 64-bit value for the bytes to read.
- // Make sure we actually read back the entire size of the file, since another process potentially could modify the size?
- DWORD BytesWritten;
- if (WriteFile(FileHandle, Memory, memorySize, &BytesWritten, 0)) {
- Result = (BytesWritten == memorySize);
- }
- // Else, handle the error case when we couldn't read the file.
- else
- {
- }
- // At the very end of the call, close the file handle.
- CloseHandle(FileHandle);
- }
- // Loggging: The file handle was not initialized. Error with reading the file.
- else {
- }
- // If everything succeeds, this should return true.
- return Result;
- }
- // Contains pointers to our game functions.
- struct win32_game_code
- {
- HMODULE GameCodeDLL;
- game_update_and_render *UpdateAndRender;
- game_get_sound_samples *GetSoundSamples;
- bool isValid;
- };
- // The platform layer is ca
- internal win32_game_code Win32LoadGameCode(void)
- {
- win32_game_code Result = {};
- // Attempt to load our library.
- Result.GameCodeDLL = LoadLibraryA("handmade.exe");
- // If we load the library successfully, get the functions we need and assign them to the stub functions created above.
- if (Result.GameCodeDLL) {
- // Gets the address of a process. Since it has no knowledge of the return type we want, we need to cast.
- 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 we didn't get the address of our processes, set them to the empty stub functions.
- if (!Result.isValid) {
- Result.UpdateAndRender = GameUpdateAndRenderStub;
- Result.GetSoundSamples = GameGetSoundSamplesStub;
- }
- return Result;
- }
- // Attempts to load a DLL needed to provide the underlying functionality for the XInput methods we use.
- internal void Win32LoadXInput(void)
- {
- // TODO: Test on Windows 8!
- HMODULE XInputLibrary = LoadLibraryA("xinput1_4.dll");
- if (!XInputLibrary)
- LoadLibraryA("xinput1_3.dll");
- if (!XInputLibrary)
- LoadLibraryA("xinput9_1_0.dll");
- // If we load the library successfully, get the functions we need and assign them to the stub functions created above.
- if (XInputLibrary) {
- // Gets the address of a process. Since it has no knowledge of the return type we want, we need to cast.
- XInputGetState = (x_input_get_state *)GetProcAddress(XInputLibrary, "XInputGetState");
- if (!XInputGetState) { XInputGetState = XInputGetStateStub; }
- XInputSetState = (x_input_set_state *)GetProcAddress(XInputLibrary, "XInputSetState");
- if (!XInputSetState) { XInputSetState = XInputSetStateStub; }
- }
- }
- // Initializes sound for our game, after a LONG series of steps.
- internal void Win32InitDSound(HWND Window, int32_t SamplesPerSecond, int32_t BufferSize)
- {
- // Load the .dll and associated vtable into memory to enable calls relating to DirectSound such as SetCooperativeLevel().
- HMODULE DSoundLibrary = LoadLibraryA("dsound.dll");
- // If we find the library, proceed.
- if (DSoundLibrary) {
- // Get a DirectSound object and set the correct mode.
- direct_sound_create *DirectSoundCreate = (direct_sound_create *)GetProcAddress(DSoundLibrary, "DirectSoundCreate");
- // If we actually get the process back and we are returned a certain value, continue.
- // Double check if this works on XP. DirectSound8 or DirectSound7?
- LPDIRECTSOUND DirectSound;
- if (DirectSoundCreate && SUCCEEDED(DirectSoundCreate(0, &DirectSound, 0))) {
- // Set the format of the primary buffer. Dields in the documentation are actually out of order, rearrange them.
- 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;
- // When our window is active, set the cooperative mode so we can can call SetFormat() later on.
- if (SUCCEEDED(DirectSound->SetCooperativeLevel(Window, DSSCL_PRIORITY))) {
- // Set the properties of the primary buffer.
- DSBUFFERDESC BufferDescription = {};
- BufferDescription.dwSize = sizeof(BufferDescription);
- BufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
- // Attempts to create actually create the primary buffer - a handle to the sound card. WE WILL NEVER USE THIS AS A BUFFER!
- LPDIRECTSOUNDBUFFER PrimaryBuffer;
- if (SUCCEEDED(DirectSound->CreateSoundBuffer(&BufferDescription, &PrimaryBuffer, 0))) {
- HRESULT Error = PrimaryBuffer->SetFormat(&WaveFormat);
- // We succeeded in making the sound card play in a format we want.
- if (SUCCEEDED(Error)) {
- OutputDebugStringA("Primary buffer format was set.\n");
- }
- // We couldn't set the properies of the primary buffer. Perform logging here.
- else {
- }
- }
- // We didn't create a primary buffer. Perform logging here.
- else {
- }
- }
- // Setting the cooperative level was not successful. Perform logging here.
- else {
- }
- // Investigate DSBCAPS_GETCURRENTPOSITION for accuracy purposes.
- DSBUFFERDESC BufferDescription = {};
- BufferDescription.dwSize = sizeof(BufferDescription);
- BufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
- BufferDescription.dwBufferBytes = BufferSize;
- BufferDescription.lpwfxFormat = &WaveFormat;
- // Attempt to create a secondary buffer, in which we can actually write to.
- HRESULT Error = DirectSound->CreateSoundBuffer(&BufferDescription, &GlobalSecondaryBuffer, 0);
- if (SUCCEEDED(Error)) {
- OutputDebugStringA("Secondary buffer was created.\n");
- }
- }
- // If we don't get the process back, break into a diagnostic.
- else {
- }
- }
- // We could not find the DSoundLibrary. Run the game, but without sound.
- else {
- }
- }
- // Returns the size of our window as a struct we define.
- 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;
- }
- // Initializes or resizes the bitmap that we write into, that Windows can then display using GDI.
- internal void Win32ResizeDeviceIndependentBitmapSection(win32_offscreen_buffer *Buffer, int width, int height)
- {
- // If we need to resize our DIB section but the bitmap memory is already allocated, free the bitmap memory.
- // VirtualProtect() could have been used here to "leak" the memory but not release to OS, to assert on access-after-free bugs.
- if (Buffer->Memory)
- VirtualFree(Buffer->Memory, 0, MEM_RELEASE);
- // Sets the size of the bitmap, along with the size of a pixel in the bitmap.
- Buffer->width = width;
- Buffer->height = height;
- int bytesPerPixel = 4;
- Buffer->bytesPerPixel = bytesPerPixel;
- // Ask Windows to make us a bitmap we can use.
- Buffer->Info.bmiHeader.biSize = sizeof(Buffer->Info.bmiHeader);
- Buffer->Info.bmiHeader.biWidth = Buffer->width;
- // When the biHeight field is negative, Windows treats the bitmap as top-down, not bottom-up.
- Buffer->Info.bmiHeader.biHeight = -Buffer->height;
- Buffer->Info.bmiHeader.biPlanes = 1;
- // Add 8 bits padding to the original 24 bits per pixel RGB for 4-byte memory alignment concerns. Performance!
- Buffer->Info.bmiHeader.biBitCount = 32;
- Buffer->Info.bmiHeader.biCompression = BI_RGB;
- // Determine how much memory we need for the bitmap.
- int bitmapMemorySize = (Buffer->width * Buffer->height) * bytesPerPixel;
- // Actually allocate the memory we need for our bitmap buffer.
- Buffer->Memory = VirtualAlloc(0, bitmapMemorySize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
- // Set the pitch, the distance from one row to the next.
- Buffer->pitch = Buffer->width * bytesPerPixel;
- }
- // Updates the window to account for it becoming dirty, obscured, or moving off screen.
- internal void Win32DisplayBufferInWindow(win32_offscreen_buffer *Buffer, HDC DeviceContext, int windowWidth, int windowHeight)
- {
- // Actually blit it to the screen. In effect, takes a rectangle of a size and copies it to another, accounting for scaling.
- // TODO: Need to correct for aspect ratio! Play with stretch modes?
- StretchDIBits(DeviceContext, 0, 0, windowWidth, windowHeight, 0, 0, Buffer->width, Buffer->height, Buffer->Memory, &Buffer->Info, DIB_RGB_COLORS, SRCCOPY);
- }
- // Anytime Windows needs something done on a window, it calls us and we need to respond.
- LRESULT CALLBACK Win32MainWindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam)
- {
- // Initially, assume we handled the message.
- LRESULT result = 0;
- // Actually handle the message, which is yet to be implemented.
- switch (Message) {
- case WM_SIZE:
- {
- OutputDebugStringA("WM_SIZE\n");
- break;
- }
- case WM_CLOSE:
- {
- globalRunning = false;
- break;
- }
- case WM_ACTIVATEAPP:
- {
- OutputDebugStringA("WM_ACTIVATEAPP\n");
- break;
- }
- case WM_DESTROY:
- {
- globalRunning = false;
- break;
- }
- case WM_SYSKEYDOWN:
- case WM_SYSKEYUP:
- case WM_KEYDOWN:
- case WM_KEYUP:
- {
- Assert("Came in through somewhere else, from a non-dispatch message.");
- } break;
- case WM_PAINT:
- {
- // BeginPaint is needed for validating a dirty region of the window - such as when it's resized.
- PAINTSTRUCT Paint;
- HDC DeviceContext = BeginPaint(Window, &Paint);
- // 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.
- win32_window_dimension Dimension = Win32GetWindowDimension(Window);
- // Pass the bounded values to the update function.
- Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, Dimension.width, Dimension.height);
- // Signal to Windows that the repaint was actually done, so it can stop asking us to repaint it.
- EndPaint(Window, &Paint);
- break;
- }
- default:
- {
- // OutputDebugStringA("default\n");
- result = DefWindowProcA(Window, Message, WParam, LParam);
- break;
- }
- }
- return result;
- }
- internal void Win32ClearBuffer(win32_sound_output *SoundOutput)
- {
- // Determine the respective sizes of the sound buffer partitions and create handles to refer to them.
- VOID *Region1;
- DWORD Region1Size;
- VOID *Region2;
- DWORD Region2Size;
- // Lock the entire sound buffer...
- if (SUCCEEDED(GlobalSecondaryBuffer->Lock(0, SoundOutput->secondaryBufferSize, &Region1, &Region1Size, &Region2, &Region2Size, 0))) {
- // and overwrite it with zeroes.
- uint8_t *destinationSample = (uint8_t *)Region1;
- for (DWORD byteIndex = 0; byteIndex < Region1Size; ++byteIndex)
- *destinationSample++ = 0;
- // 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.
- destinationSample = (uint8_t *)Region2;
- for (DWORD byteIndex = 0; byteIndex < Region2Size; ++byteIndex)
- *destinationSample++ = 0;
- // Unlock the sound buffer for writing.
- GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size);
- }
- }
- internal void Win32FillSoundBuffer(win32_sound_output *SoundOutput, DWORD BytesToLock, DWORD BytesToWrite, game_sound_output_buffer *SourceBuffer)
- {
- // Determine the respective sizes of the sound buffer partitions and create handles to refer to them.
- VOID *Region1;
- DWORD Region1Size;
- VOID *Region2;
- DWORD Region2Size;
- // Lock to write to the sound buffer containing samples [int16_t int16_t] at wherever our sample index is.
- if (SUCCEEDED(GlobalSecondaryBuffer->Lock(BytesToLock, BytesToWrite, &Region1, &Region1Size, &Region2, &Region2Size, 0))) {
- // Determine the number of samples to iterate over, depending on the respective buffer partitions.
- DWORD Region1SampleCount = Region1Size / SoundOutput->bytesPerSample;
- int16_t *sourceSample = SourceBuffer->Samples;
- int16_t *destinationSample = (int16_t *)Region1;
- for (DWORD sampleIndex = 0; sampleIndex < Region1SampleCount; ++sampleIndex) {
- // Copy the samples from our source buffer to our destination buffer.
- *destinationSample++ = *sourceSample++;
- *destinationSample++ = *sourceSample++;
- ++SoundOutput->runningSampleIndex;
- }
- DWORD Region2SampleCount = Region2Size / SoundOutput->bytesPerSample;
- destinationSample = (int16_t *)Region2;
- for (DWORD sampleIndex = 0; sampleIndex < Region2SampleCount; ++sampleIndex) {
- // Copy the samples from our source buffer to our destination buffer.
- *destinationSample++ = *sourceSample++;
- *destinationSample++ = *sourceSample++;
- ++SoundOutput->runningSampleIndex;
- }
- // Unlock the sound buffer for writing.
- GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size);
- }
- }
- internal void Win32ProcessKeyboardMessage(game_button_state *NewState, bool isDown)
- {
- // Check that we only enter this method when the pressed state of a key changes.
- Assert(NewState->endedDown != isDown);
- // The state of the buttons pressed down at the time of polling.
- NewState->endedDown = isDown;
- // If the button ended down, there was a half transition before. Update and increment the total number of half transition counts.
- ++NewState->halfTransitionCount;
- }
- internal void Win32ProcessXInputDigitalButton(DWORD XInputButtonState, game_button_state *OldState, DWORD ButtonBit, game_button_state *NewState)
- {
- // The state of the buttons pressed down at the time of polling.
- NewState->endedDown = ((XInputButtonState & ButtonBit) == ButtonBit);
- // If the button ended down, there was a half transition before. Otherwise, there was no half transition.
- NewState->halfTransitionCount = (OldState->endedDown != NewState->endedDown) ? 1 : 0;
- }
- internal float Win32ProcessXInputStickValue(SHORT value, SHORT deadzoneThreshold)
- {
- float result = 0;
- /** Implements a smooth progression at the border of deadzone, instead of a jump to 0.2. */
- if (value <= -deadzoneThreshold)
- result = (float)((value + deadzoneThreshold) / (32768.0f - deadzoneThreshold));
- else if (value > deadzoneThreshold)
- result = (float)((value - deadzoneThreshold) / (32767.0f - deadzoneThreshold));
- return result;
- }
- internal void Win32ProcessPendingMessages(game_controller_input *KeyboardController)
- {
- // If our message is a keyboard message, don't ask Windows to translate the message!
- // PeekMessage() allows us to keep globalRunning when there are no messages to process, instead of blocking. PM_REMOVE removes messages as they are processed.
- MSG Message;
- while (PeekMessage(&Message, 0, 0, 0, PM_REMOVE)) {
- switch (Message.message) {
- // Stop the game if we get a quit message from Windows.
- case WM_QUIT:
- {
- globalRunning = false;
- } break;
- case WM_SYSKEYDOWN:
- case WM_SYSKEYUP:
- case WM_KEYDOWN:
- case WM_KEYUP:
- {
- // A VKCode is a representation of a keyboard button with no ANSI equialent. Windows feeds sends us these via WParam.
- // Skip dispatching the messages from Windows, and get the message directly from the MSG structure.
- uint32_t VKCode = (uint32_t)Message.wParam;
- // lParam is a bitfield. The 30th bit indicates if the key was previously held down.
- bool wasDown = ((Message.lParam & (1 << 30)) != 0);
- bool 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 == 'S') {
- Win32ProcessKeyboardMessage(&KeyboardController->moveRight, isDown);
- }
- else if (VKCode == 'D') {
- Win32ProcessKeyboardMessage(&KeyboardController->moveDown, 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_DOWN) {
- Win32ProcessKeyboardMessage(&KeyboardController->actionDown, isDown);
- }
- else if (VKCode == VK_LEFT) {
- Win32ProcessKeyboardMessage(&KeyboardController->actionLeft, 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;
- }
- }
- #endif
- }
- // Determine if the [Alt] key was held down. If so, quit he game if [F4] is also pressed.
- int32_t altKeyWasDown = (Message.lParam & (1 << 29));
- if ((VKCode == VK_F4) && altKeyWasDown) {
- globalRunning = false;
- }
- } break;
- default:
- {
- // Translate and dispatch the message. Turns keyboard messages into more "proper" keyboard messages.
- TranslateMessage(&Message);
- DispatchMessage(&Message);
- }
- }
- }
- }
- LARGE_INTEGER Win32GetWallClock(void)
- {
- // Finish measuring wall clock time and cycle count.
- LARGE_INTEGER result;
- QueryPerformanceCounter(&result);
- return result;
- }
- inline float Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End)
- {
- // How much time has actually elapsed so far?
- float result = ((float)(End.QuadPart - Start.QuadPart) / (float)globalPerformanceCounterFrequency);
- return result;
- }
- internal void Win32DebugDrawVertical(win32_offscreen_buffer *Backbuffer, int x, int top, int bottom, uint32_t color)
- {
- // Check that we don't draw above the top or below the bottom. If we do, clip.
- if (top <= 0)
- top = 0;
- if (bottom >= Backbuffer->height)
- bottom = Backbuffer->height;
- // Check that we don't draw past our buffer. If we don't, actually draw.
- if ((x >= 0) && (x < Backbuffer->width)) {
- uint8_t *Pixel = ((uint8_t *)Backbuffer->Memory + x * Backbuffer->bytesPerPixel + top * Backbuffer->pitch);
- for (int y = top; y < bottom; ++y) {
- *(uint32_t *)Pixel = color;
- Pixel += Backbuffer->pitch;
- }
- }
- }
- /** Actually draw where our desired frame flips are relative to where our audio is playing. */
- inline void Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer, win32_sound_output *SoundOutput, float scalingFactor, int padX, int top, int bottom, DWORD Value, uint32_t Color)
- {
- float xfloat32 = (scalingFactor * (float)Value);
- int x = padX + (int)xfloat32;
- Win32DebugDrawVertical(Backbuffer, x, top, bottom, Color);
- }
- /** Draw where our desired frame flips are relative to where our audio is playing. */
- internal void Win32DebugSyncDisplay(win32_offscreen_buffer *Backbuffer, int MarkerCount, win32_debug_time_marker *Markers, int currentMarkerIndex, win32_sound_output *SoundOutput, float targetSecondsPerFrame)
- {
- // We need to scale our drawing rectangle from bytes to pixels. Pad this value, so the whole area can be seen on screen.
- int padX = 16;
- int padY = 16;
- int lineHeight = 64;
- float scalingFactor = (float)(Backbuffer->width - 2 * padX) / (float)(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);
- // 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.
- DWORD PlayColor = 0xFFFFFFFF;
- DWORD WriteColor = 0xFFFF0000;
- DWORD ExpectedFlipColor = 0xFFFFFF00;
- DWORD PlayWindowColor = 0xFFFF00FF;
- // Controls the pading for how the lines are drawn.
- int top = padY;
- int bottom = padY + lineHeight;
- if (markerIndex == currentMarkerIndex) {
- top += lineHeight + padY;
- bottom += lineHeight + padY;
- int firstTop = top;
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputPlayCursor, PlayColor);
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputWriteCursor, WriteColor);
- top += lineHeight + padY;
- bottom += lineHeight + padY;
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputLocation, PlayColor);
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->OutputLocation + ThisMarker->OutputByteCount, WriteColor);
- top += lineHeight + padY;
- bottom += lineHeight + padY;
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, firstTop, bottom, ThisMarker->ExpectedFlipPlayCursor, ExpectedFlipColor);
- }
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->FlipPlayCursor, PlayColor);
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->FlipPlayCursor + 480 * SoundOutput->bytesPerSample, PlayWindowColor);
- Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, scalingFactor, padX, top, bottom, ThisMarker->FlipWriteCursor, WriteColor);
- }
- }
- // Insert the entry point for Windows, importantly where the handle to our executable is.
- int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
- {
- // Attempt to load the DLL that contains our game-specific functions.
- win32_game_code Game = Win32LoadGameCode();
- // Determine how many clock cycles the CPU goes through in a single second.
- LARGE_INTEGER PerformanceCounterFrequencyResult;
- QueryPerformanceFrequency(&PerformanceCounterFrequencyResult);
- int64_t globalPerformanceCounterFrequency = PerformanceCounterFrequencyResult.QuadPart;
- // Set the scheduler polling period to 1 msec, so sleep doesn't block excessively. Check to see if it was able to be set.
- UINT desiredSchedulerMS = 1;
- bool sleepIsNonBlocking = (timeBeginPeriod(desiredSchedulerMS) == TIMERR_NOERROR);
- // Controller and window initialization.
- Win32LoadXInput();
- WNDCLASS WindowClass = {};
- Win32ResizeDeviceIndependentBitmapSection(&GlobalBackbuffer, 1280, 720);
- WindowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
- WindowClass.lpfnWndProc = Win32MainWindowCallback;
- WindowClass.hInstance = hInstance;
- WindowClass.lpszClassName = "HandmadeHeroWindowClass";
- // Todo: How do we query this on Windows?
- #define monitorRefreshHz 60
- #define gameUpdateHz (monitorRefreshHz / 2)
- float targetSecondsPerFrame = 1.0f / (float)gameUpdateHz;
- // Register the window class so that we can open a window. This can fail, so check if it succeds.
- if (RegisterClass(&WindowClass)) {
- // Create the window, using the handle that we specify.
- HWND Window = CreateWindowEx(0, WindowClass.lpszClassName, "Handmade Hero", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0);
- // Checks to see if the window was created successfully.
- if (Window) {
- // Since we specified CS_OWNDC, we get one device context and use it exclusively.
- HDC DeviceContext = GetDC(Window);
- // Graphics test.
- int xOffset = 0;
- int yOffset = 0;
- // Initialize sound.
- win32_sound_output SoundOutput = {};
- SoundOutput.samplesPerSecond = 48000;
- SoundOutput.bytesPerSample = (sizeof(int16_t) * 2);
- SoundOutput.secondaryBufferSize = SoundOutput.samplesPerSecond * SoundOutput.bytesPerSample;
- // Todo: Get rid of latencySampleCount.
- SoundOutput.latencySampleCount = 3 * (SoundOutput.samplesPerSecond / gameUpdateHz);
- // Todo: Actually compute this value and see what the lowest bound is.
- SoundOutput.safetyBytes = (SoundOutput.samplesPerSecond * SoundOutput.bytesPerSample / gameUpdateHz) / 3;
- Win32InitDSound(Window, SoundOutput.samplesPerSecond, SoundOutput.secondaryBufferSize);
- Win32ClearBuffer(&SoundOutput);
- GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
- globalRunning = true;
- #if 0
- // Testing: Locks the program so we can observe the sound card write granularity.
- 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
- // On the stack, reserve memory to hold the samples used in our sound buffer.
- // Todo: Pool this memory with the bitmap VirtualAlloc() into a seperate call.
- int16_t *Samples = (int16_t *)VirtualAlloc(0, SoundOutput.secondaryBufferSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
- // 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.
- #if HANDMADE_INTERNAL
- LPVOID BaseAddress = (LPVOID)terabytes((uint64_t)2);
- #else
- LPVOID BaseAddress = 0;
- #endif
- // Declare a buffer to hold our game memory and allocate memory to our permanent storage and transient storage partitions.
- // Dev: On 32-bit systems, need to account for integral promotion, hence the (uint64_t) cast in 450.
- game_memory GameMemory = {};
- GameMemory.permanantStorageSize = megabytes(64);
- GameMemory.transientStorageSize = gigabytes(1);
- GameMemory.DEBUGPlatformFreeFileMemory = DEBUGPlatformFreeFileMemory;
- GameMemory.DEBUGPlatformReadEntireFile = DEBUGPlatformReadEntireFile;
- GameMemory.DEBUGPlatformWriteEntireFile = DEBUGPlatformWriteEntireFile;
- uint64_t totalSize = GameMemory.permanantStorageSize + GameMemory.transientStorageSize;
- GameMemory.permanentStorage = VirtualAlloc(BaseAddress, (size_t)totalSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
- // Cast to a byte pointer and advance exactly past where the permanent storage size memory partion ends.
- GameMemory.transientStorage = ((uint8_t *)GameMemory.permanentStorage + GameMemory.permanantStorageSize);
- // Check to see if the sound and game memory have been allocated correctly.
- if (Samples && GameMemory.permanentStorage && GameMemory.transientStorage) {
- // Declare two buffers to store our old input and new input state, clear initially.
- game_input Input[2] = {};
- game_input *NewInput = &Input[0];
- game_input *OldInput = &Input[1];
- // Start measuring wall clock time. Generate the processor cycle count since the last reset using __rdtsc.
- LARGE_INTEGER LastCounter = Win32GetWallClock();
- LARGE_INTEGER flipWallClock = Win32GetWallClock();
- int DebugTimeMarkerIndex = 0;
- win32_debug_time_marker DebugTimeMarkers[gameUpdateHz / 2] = {0};
- DWORD audioLatencyInBytes = 0;
- float audioLatencyInSeconds = 0;
- bool soundIsValid = false;
- uint64_t lastCycleCount = __rdtsc();
- // Loop through the message queue and send them to the window.
- while (globalRunning) {
- // Let's have the keyboard come in as the first controller.
- // Todo: Make a zeroing macro at some point, otherise the previous keyup and keydown state will be wrong.
- game_controller_input *OldKeyboardController = GetController(OldInput, 0);
- game_controller_input *NewKeyboardController = GetController(NewInput, 0);
- *NewKeyboardController = {};
- NewKeyboardController->isConnected = true;
- // Maps the up/down state of the keyboard from the previous frame to the current frame, to not lose state.
- for (int buttonIndex = 0; buttonIndex < elementsInArray(NewKeyboardController->Buttons); ++buttonIndex) {
- NewKeyboardController->Buttons[buttonIndex].endedDown = OldKeyboardController->Buttons[buttonIndex].endedDown;
- }
- Win32ProcessPendingMessages(NewKeyboardController);
- // Don't run if we're paused.
- if (!globalPause) {
- // Todo: Don't poll disconnected controllers to avoid XInput frame rate hit on older libraries.
- DWORD maxControllerCount = XUSER_MAX_COUNT;
- if (maxControllerCount > (elementsInArray(NewInput->Controllers) - 1)) {
- maxControllerCount = (elementsInArray(NewInput->Controllers) - 1);
- }
- // Loop over all the XInput controllers and poll them.
- for (DWORD ControllerIndex = 0; ControllerIndex < maxControllerCount; ++ControllerIndex) {
- // Write the state of the controller's previous input, and the current input to be processed.
- int ourControllerIndex = ControllerIndex + 1;
- game_controller_input *OldController = GetController(OldInput, ourControllerIndex);
- game_controller_input *NewController = GetController(NewInput, ourControllerIndex);
- // Holds the state of the controller we specify.
- XINPUT_STATE ControllerState;
- // The controller is plugged in. See if dwPacketumber increments too rapidly, meaning we need to poll more often.
- if (XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS) {
- NewController->isConnected = true;
- // Defines a simple way to refer to the controller.
- XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad;
- // Todo: this is a square deadzone, check XInput to check if the deadzone is circular. Implement if possible.
- NewController->stickAverageX = Win32ProcessXInputStickValue(Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
- NewController->stickAverageY = Win32ProcessXInputStickValue(Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
- if ((NewController->stickAverageX != 0.0f) || (NewController->stickAverageY != 0.0f))
- NewController->isAnalog = true;
- // Accounts for d-pad handling.
- 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;
- }
- // Decrypts what button on the controller is being pressed.
- float 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);
- }
- // The controller is not available. It is not always a error.
- else {
- NewController->isConnected = false;
- }
- }
- // Update the game loop.
- game_offscreen_buffer Buffer = {};
- Buffer.Memory = GlobalBackbuffer.Memory;
- Buffer.width = GlobalBackbuffer.width;
- Buffer.height = GlobalBackbuffer.height;
- Buffer.pitch = GlobalBackbuffer.pitch;
- Game.UpdateAndRender(&GameMemory, NewInput, &Buffer);
- LARGE_INTEGER audioWallClock = Win32GetWallClock();
- float fromFrameBeginToAudioInSeconds = Win32GetSecondsElapsed(flipWallClock, audioWallClock);
- DWORD PlayCursor;
- DWORD WriteCursor;
- if (GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) {
- // If the sound buffer is valid, start writing at the write cursor, we otherwise start writing where we left off.
- if (!soundIsValid) {
- SoundOutput.runningSampleIndex = WriteCursor / SoundOutput.bytesPerSample;
- soundIsValid = true;
- }
- // 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).
- DWORD BytesToLock = ((SoundOutput.runningSampleIndex * SoundOutput.bytesPerSample) % SoundOutput.secondaryBufferSize);
- DWORD expectedSoundBytesPerFrame = (SoundOutput.samplesPerSecond * SoundOutput.bytesPerSample) / gameUpdateHz;
- float secondsLeftUntilFlip = targetSecondsPerFrame - fromFrameBeginToAudioInSeconds;
- DWORD expectedBytesUntilFlip = ((DWORD)(secondsLeftUntilFlip - fromFrameBeginToAudioInSeconds) * (float)expectedSoundBytesPerFrame);
- DWORD expectedFrameBoundaryByte = PlayCursor + expectedSoundBytesPerFrame;
- // Assume that our audio card is latent. It is latent if our safety margin falls after a frame boundary flip.
- DWORD SafeWriteCursor = WriteCursor;
- // Normalize the our safe write cursor to always be ahead of the write cursor.
- if (SafeWriteCursor < PlayCursor)
- SafeWriteCursor += SoundOutput.secondaryBufferSize;
- Assert(SafeWriteCursor >= PlayCursor);
- // If our safety margin determines us to be over a frame boundary, the audio card is latent.
- SafeWriteCursor = SafeWriteCursor + SoundOutput.safetyBytes;
- bool audioCardIsLowLatency = (SafeWriteCursor < expectedFrameBoundaryByte);
- /* 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
- number of samples determined by the safety margin. This is in the case of a latent audio card.
- */
- DWORD TargetCursor = 0;
- if (audioCardIsLowLatency) {
- TargetCursor = (expectedFrameBoundaryByte + expectedSoundBytesPerFrame);
- }
- /* 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.
- look to see if the write cursor is before the forecast position. If it is, and before a predetermined safety margin
- (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
- one frame further - gives perfrect audio sync. This is in the case of a non-latent audio card.
- */
- else {
- TargetCursor = WriteCursor + expectedSoundBytesPerFrame + SoundOutput.safetyBytes;
- }
- // Make sure we always map to within the boundaries of the sound buffer.
- TargetCursor = TargetCursor % SoundOutput.secondaryBufferSize;
- DWORD BytesToWrite = 0;
- if (BytesToLock > TargetCursor) {
- // 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.
- BytesToWrite = (SoundOutput.secondaryBufferSize - BytesToLock) + TargetCursor;
- }
- else {
- // BytesToLock is behind the play cursor, we have a single partition sitation. Write up to the beginning of the play cursor, trailing it.
- BytesToWrite = TargetCursor - BytesToLock;
- }
- // Initialize the sound buffer and write the appropriate number of sound samples to the appopriate place in the sound buffer.
- game_sound_output_buffer SoundBuffer = {};
- SoundBuffer.samplesPerSecond = SoundOutput.samplesPerSecond;
- SoundBuffer.sampleCount = BytesToWrite / SoundOutput.bytesPerSample;
- SoundBuffer.Samples = Samples;
- Game.GetSoundSamples(&GameMemory, &SoundBuffer);
- #if HANDMADE_INTERNAL
- // Update and write information regarding debug time markers.
- win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex];
- Marker->OutputPlayCursor = PlayCursor;
- Marker->OutputWriteCursor = WriteCursor;
- Marker->OutputLocation = BytesToLock;
- Marker->OutputByteCount = BytesToWrite;
- Marker->ExpectedFlipPlayCursor = expectedFrameBoundaryByte;
- // 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.
- DWORD UnwrappedWriteCursor = WriteCursor;
- if (UnwrappedWriteCursor < PlayCursor)
- UnwrappedWriteCursor += SoundOutput.secondaryBufferSize;
- // Compute the latency of the sound card in bytes, using where we play sound and where we write samples.
- audioLatencyInBytes = UnwrappedWriteCursor - PlayCursor;
- // Using the above, reason about an audio latency in seconds.
- audioLatencyInSeconds = (((float)audioLatencyInBytes / (float)SoundOutput.bytesPerSample) / (float)SoundOutput.samplesPerSecond);
- // When we filled the sound buffer, where did it think we were?
- char TextBuffer[256];
- _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);
- OutputDebugStringA(TextBuffer);
- #endif
- Win32FillSoundBuffer(&SoundOutput, BytesToLock, BytesToWrite, &SoundBuffer);
- }
- else {
- soundIsValid = false;
- }
- LARGE_INTEGER WorkCounter = Win32GetWallClock();
- float workSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter);
- // Until we use up all the time we render our frames at, don't flip.
- float secondsElapsedForFrame = workSecondsElapsed;
- if (secondsElapsedForFrame < targetSecondsPerFrame) {
- // Only sleep if we were able to set the scheduler polling period to 1 msec.
- if (sleepIsNonBlocking) {
- DWORD sleepMS = (DWORD)(1000.0f * (targetSecondsPerFrame - secondsElapsedForFrame));
- if (sleepMS > 0)
- Sleep(sleepMS);
- }
- // Todo: Check that we didn't sleep for too long and miss a frame.
- float testSecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock());
- if (testSecondsElapsedForFrame < targetSecondsPerFrame) {
- // If we did, we need to log a missed sleep.
- }
- while (secondsElapsedForFrame < targetSecondsPerFrame)
- secondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, Win32GetWallClock());
- }
- else {
- // Todo: We missed our frame rate! Need to log this.
- }
- // After the lock, benchmark the next pass through the loop.
- LARGE_INTEGER EndCounter = Win32GetWallClock();
- float millisecondsPerFrame = 1000.0f * Win32GetSecondsElapsed(LastCounter, EndCounter);
- LastCounter = EndCounter;
- // After getting the size of the window, actually blit to our screen.
- win32_window_dimension Dimension = Win32GetWindowDimension(Window);
- #if HANDMADE_INTERNAL
- // Note: (DebugTimeMarkers - 1), which is the current marker being drawn, is wrong on the 0th index.
- Win32DebugSyncDisplay(&GlobalBackbuffer, elementsInArray(DebugTimeMarkers), DebugTimeMarkers, DebugTimeMarkerIndex - 1, &SoundOutput, targetSecondsPerFrame);
- #endif
- Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, Dimension.width, Dimension.height);
- // Get the time right after the frame flip.
- flipWallClock = Win32GetWallClock();
- #if HANDMADE_INTERNAL
- {
- DWORD PlayCursor;
- DWORD WriteCursor;
- if (GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK) {
- Assert(DebugTimeMarkerIndex < elementsInArray(DebugTimeMarkers));
- win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex];
- // Always have where the play and write cursors are when the frame flips.
- Marker->FlipPlayCursor = PlayCursor;
- Marker->FlipWriteCursor = WriteCursor;
- }
- }
- #endif
- // Swap the state of our old and new inputs.
- game_input *Temp = NewInput;
- NewInput = OldInput;
- OldInput = Temp;
- // Other logging information.
- uint64_t endCycleCount = __rdtsc();
- uint64_t cyclesElapsed = endCycleCount - lastCycleCount;
- lastCycleCount = endCycleCount;
- float framesPerSecond = 0.0f;
- float megaCyclesPerFrame = (float)cyclesElapsed / 1000000;
- // Display the logging results to the internal console.
- char FPSBuffer[256];
- _snprintf_s(FPSBuffer, sizeof(FPSBuffer), "ms/frame: %.02fms fps: %.02fFPS: mc/f: %.02fMC/f\n", millisecondsPerFrame, framesPerSecond, megaCyclesPerFrame);
- OutputDebugStringA(FPSBuffer);
- #if HANDMADE_INTERNAL
- // Increment the index of the debug cursor and reset the index if we exceed our pretermined value - [gameUpdateHz / 2] in this case.
- ++DebugTimeMarkerIndex;
- if (DebugTimeMarkerIndex == elementsInArray(DebugTimeMarkers))
- DebugTimeMarkerIndex = 0;
- #endif
- }
- }
- }
- // Todo: Perform logging for sound and memory and allocation check.
- else {}
- }
- // Todo: Perform logging for Window.
- else {}
- }
- // Todo: Perform logging for registering WindowClass.
- else {}
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement