diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..e46d1b5 --- /dev/null +++ b/.clangd @@ -0,0 +1,5 @@ +CompileFlags: + Add: + - -DHANDMADE_INTERNAL + - -DHANDMADE_SLOW + - -DHANDMADE_WIN32 diff --git a/misc/build.bat b/misc/build.bat index f9ad3ca..6bd865e 100644 --- a/misc/build.bat +++ b/misc/build.bat @@ -14,7 +14,7 @@ cl ^ -FC %= Full path of source code file in diagnostics =%^ -Zi %= Generate debugger info =%^ -Fe:handmade.exe ..\src\win32_handmade.cpp %= Output filename, input filename =%^ - user32.lib Gdi32.lib %= Linked libraries =%^ + user32.lib Gdi32.lib winmm.lib %= Linked libraries =%^ /link -subsystem:windows,5.1 %= Linker stuff =% popd exit /b diff --git a/src/handmade.h b/src/handmade.h index f51261f..bde946a 100644 --- a/src/handmade.h +++ b/src/handmade.h @@ -26,7 +26,7 @@ */ #if HANDMADE_SLOW -#define Assert(Expression) if (!(Expression)) {*(int *)0 = 0;} +#define Assert(Expression) if (!(Expression)) {*(volatile int *)0 = 0;} #else #define Assert(Expression) #endif diff --git a/src/win32_handmade.cpp b/src/win32_handmade.cpp index 35cb70d..0341c62 100644 --- a/src/win32_handmade.cpp +++ b/src/win32_handmade.cpp @@ -3,6 +3,7 @@ #include #include +// Local imports #include "win32_handmade.h" #include "handmade.cpp" @@ -10,6 +11,7 @@ global ATOM HH_CTRLW; global bool globalRunning; global Win32OffscreenBuffer globalBackBuffer; global LPDIRECTSOUNDBUFFER globalSecondaryBuffer; +global int64 globalPerfCountFrequency; // XInputGetState #define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState) @@ -91,6 +93,7 @@ internal void resizeDIBSection(Win32OffscreenBuffer *buffer, int width, int heig buffer->width = width; buffer->height = height; + buffer->bytesPerPixel = 4; buffer->info.bmiHeader.biSize = sizeof(buffer->info.bmiHeader); buffer->info.bmiHeader.biWidth = buffer->width; @@ -351,10 +354,62 @@ internal void win32ProcessPendingMessages(GameControllerInput *keyboardControlle } } +inline LARGE_INTEGER win32GetWallClock() { + LARGE_INTEGER result; + QueryPerformanceCounter(&result); + return result; +} + +inline real32 win32GetSecondsElapsed(LARGE_INTEGER start, LARGE_INTEGER end) { + return (real32)(end.QuadPart - start.QuadPart) / (real32)globalPerfCountFrequency; +} + +// int monitorRefreshHz = 60; +// int gameUpdateHz = monitorRefreshHz / 2; + +#define framesOfAudioLatency 3 +#define monitorRefreshHz 60 +#define gameUpdateHz (monitorRefreshHz / 2) + +internal void win32DebugDrawVertical(Win32OffscreenBuffer *screenBuffer, int x, int top, int bottom, int32 color) { + int pitch = screenBuffer->width*screenBuffer->bytesPerPixel; + uint8 *pixel = (uint8 *)screenBuffer->memory + top * pitch + x*screenBuffer->bytesPerPixel; + for (int y = top; y < bottom; y++) { + *(uint32 *)pixel = color; + pixel += pitch; + } +} + +inline void win32DrawSoundBufferMarker(Win32OffscreenBuffer *buffer, Win32SoundOutput *soundOutput, real32 pxPerSoundBufferEntry, int padX, int top, int bottom, DWORD value, uint32 color) { + Assert(value < (DWORD)soundOutput->secondaryBufferSize); + int x = padX + (int)(pxPerSoundBufferEntry * (real32)value); + win32DebugDrawVertical(buffer, x, top, bottom, color); +} + +internal void win32DebugSyncDisplay(Win32OffscreenBuffer *screenBuffer, int markerCount, Win32DebugTimeMarker *markers, Win32SoundOutput *soundOutput, real32 targetSecondsPerFrame) { + int padX = 16; + int padY = 16; + + int top = padY; + int bottom = screenBuffer->height - padY; + int renderWidth = screenBuffer->width - 2 *padX; + + real32 pxPerSoundBufferEntry = (real32)renderWidth / (real32)soundOutput->secondaryBufferSize; + + for (int markerIndex = 0; markerIndex < markerCount; markerIndex++) { + Win32DebugTimeMarker *thisMarker = &markers[markerIndex]; + win32DrawSoundBufferMarker(screenBuffer, soundOutput, pxPerSoundBufferEntry, padX, top, bottom, thisMarker->playCursor, 0xFFFFFFFF); + win32DrawSoundBufferMarker(screenBuffer, soundOutput, pxPerSoundBufferEntry, padX, top, bottom, thisMarker->writeCursor, 0xFFFF0000); + } +} + int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int commandShow) { LARGE_INTEGER performanceFrequencyResult; QueryPerformanceFrequency(&performanceFrequencyResult); - int64 performanceFrequency = performanceFrequencyResult.QuadPart; + globalPerfCountFrequency = performanceFrequencyResult.QuadPart; + + UINT desiredSchedulerMs = 1; + bool32 sleepIsGranular = TIMERR_NOERROR == timeBeginPeriod(desiredSchedulerMs); WNDCLASSA windowClass = {}; windowClass.style = CS_VREDRAW | CS_HREDRAW; @@ -364,6 +419,8 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin resizeDIBSection(&globalBackBuffer, 1280, 720); + real32 targetSecondsPerFrame = 1.0f / (real32)gameUpdateHz; + if (RegisterClass(&windowClass)) { HWND window = CreateWindowExA( NULL, @@ -388,6 +445,9 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin GameInput *oldInput = &input[0]; GameInput *newInput = &input[1]; + int debugTimeMarkerIndex = 0; + Win32DebugTimeMarker debugMarkers[gameUpdateHz / 2] = {}; + win32LoadXInput(); // sound test @@ -396,7 +456,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin soundOutput.runningSampleIndex = 0; soundOutput.bytesPerSample = sizeof(int16)*2; soundOutput.secondaryBufferSize = soundOutput.samplesPerSecond*soundOutput.bytesPerSample; - soundOutput.latencySampleCount = (int)(soundOutput.samplesPerSecond / 15.0f); + soundOutput.latencySampleCount = framesOfAudioLatency * (soundOutput.samplesPerSecond / gameUpdateHz); int16 *samples = (int16*)VirtualAlloc(NULL, soundOutput.secondaryBufferSize, MEM_COMMIT, PAGE_READWRITE); @@ -417,9 +477,13 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin win32ClearBuffer(&soundOutput); globalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); - LARGE_INTEGER lastCounter; - QueryPerformanceCounter(&lastCounter); + LARGE_INTEGER lastWorkCounter = win32GetWallClock(); + int64 lastCycleCount = __rdtsc(); + + bool32 soundIsValid = false; + DWORD lastPlayCursor = 0; + while (globalRunning) { GameControllerInput *oldKeyboardController = &oldInput->controllers[0]; GameControllerInput *newKeyboardController = &newInput->controllers[0]; @@ -499,15 +563,12 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin videoBuffer.height = globalBackBuffer.height; // Sound test - DWORD playCursor = 0; - DWORD writeCursor = 0; DWORD byteToLock = 0; - DWORD targetCursor = 0; + DWORD targetCursor = lastPlayCursor; DWORD bytesToWrite = 0; - bool soundIsValid = true; - if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) { + if (soundIsValid) { byteToLock = (soundOutput.runningSampleIndex*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize; - targetCursor = (playCursor + soundOutput.latencySampleCount*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize; + targetCursor = (lastPlayCursor + soundOutput.latencySampleCount*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize; if (byteToLock == targetCursor) { bytesToWrite = 0; } else if (byteToLock > targetCursor) { @@ -515,7 +576,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin } else { bytesToWrite = targetCursor - byteToLock; } - soundIsValid = true; } GameSoundOutputBuffer soundBuffer = {}; @@ -524,23 +584,66 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin soundBuffer.samples = samples; gameUpdateAndRender(&gameMemory, &videoBuffer, newInput, &soundBuffer); - if (soundIsValid) { win32FillSoundBuffer(&soundOutput, byteToLock, bytesToWrite, &soundBuffer); } + + real32 secondsElapsedForFrame = win32GetSecondsElapsed(lastWorkCounter, win32GetWallClock()); + if (secondsElapsedForFrame < targetSecondsPerFrame) { + if (sleepIsGranular) { + DWORD sleepMs = (DWORD)(1000.0f * (targetSecondsPerFrame - secondsElapsedForFrame)); + if (sleepMs > 0) { + Sleep(sleepMs); + } + } + while (secondsElapsedForFrame < targetSecondsPerFrame) { + secondsElapsedForFrame = win32GetSecondsElapsed(lastWorkCounter, win32GetWallClock()); + } + } else { + // logging, MISSED FRAME! + } + + lastWorkCounter = win32GetWallClock(); + +#if HANDMADE_INTERNAL + win32DebugSyncDisplay(&globalBackBuffer, ArrayCount(debugMarkers), debugMarkers, &soundOutput, targetSecondsPerFrame); +#endif + win32DrawBufferInWindow(&globalBackBuffer, window); - LARGE_INTEGER endCounter; - QueryPerformanceCounter(&endCounter); + DWORD playCursor = 0; + DWORD writeCursor = 0; + if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) { + lastPlayCursor = playCursor; + if (!soundIsValid) { + soundOutput.runningSampleIndex = writeCursor / soundOutput.bytesPerSample; + soundIsValid = true; + } + } else { + soundIsValid = false; + } + +#if HANDMADE_INTERNAL + { + Assert(debugTimeMarkerIndex < ArrayCount(debugMarkers)); + + Win32DebugTimeMarker *marker = &debugMarkers[debugTimeMarkerIndex++]; + + if (debugTimeMarkerIndex >= ArrayCount(debugMarkers)) { + debugTimeMarkerIndex = 0; + } + + globalSecondaryBuffer->GetCurrentPosition(&marker->playCursor, &marker->writeCursor); + } +#endif + +#if 0 + real64 msElapsed = (real32)(1000.0f*secondsElapsedForFrame) / globalPerfCountFrequency; + real64 fps = (real32)(1000.0f*globalPerfCountFrequency/(real32)secondsElapsedForFrame)/1000.0f; int64 endCycleCount = __rdtsc(); int64 cyclesElapsed = endCycleCount - lastCycleCount; lastCycleCount = endCycleCount; - - int64 counterElapsed = endCounter.QuadPart - lastCounter.QuadPart; - real32 msElapsed = (real32)(1000.0f*counterElapsed) / performanceFrequency; - real32 fps = (real32)(1000.0f*performanceFrequency/(real32)counterElapsed)/1000.0f; - - lastCounter = endCounter; +#endif GameInput *temp = newInput; newInput = oldInput; @@ -552,6 +655,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin } else { // failed } - return(0); + + return 0; } diff --git a/src/win32_handmade.h b/src/win32_handmade.h index fc26345..c1f1250 100644 --- a/src/win32_handmade.h +++ b/src/win32_handmade.h @@ -22,3 +22,8 @@ struct Win32SoundOutput { real32 tSine; int latencySampleCount; }; + +struct Win32DebugTimeMarker { + DWORD playCursor; + DWORD writeCursor; +};