|
@@ -3,6 +3,7 @@ |
|
|
#include <Xinput.h> |
|
|
#include <Xinput.h> |
|
|
#include <dsound.h> |
|
|
#include <dsound.h> |
|
|
|
|
|
|
|
|
|
|
|
// Local imports |
|
|
#include "win32_handmade.h" |
|
|
#include "win32_handmade.h" |
|
|
#include "handmade.cpp" |
|
|
#include "handmade.cpp" |
|
|
|
|
|
|
|
@@ -10,6 +11,7 @@ global ATOM HH_CTRLW; |
|
|
global bool globalRunning; |
|
|
global bool globalRunning; |
|
|
global Win32OffscreenBuffer globalBackBuffer; |
|
|
global Win32OffscreenBuffer globalBackBuffer; |
|
|
global LPDIRECTSOUNDBUFFER globalSecondaryBuffer; |
|
|
global LPDIRECTSOUNDBUFFER globalSecondaryBuffer; |
|
|
|
|
|
global int64 globalPerfCountFrequency; |
|
|
|
|
|
|
|
|
// XInputGetState |
|
|
// XInputGetState |
|
|
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState) |
|
|
#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->width = width; |
|
|
buffer->height = height; |
|
|
buffer->height = height; |
|
|
|
|
|
buffer->bytesPerPixel = 4; |
|
|
|
|
|
|
|
|
buffer->info.bmiHeader.biSize = sizeof(buffer->info.bmiHeader); |
|
|
buffer->info.bmiHeader.biSize = sizeof(buffer->info.bmiHeader); |
|
|
buffer->info.bmiHeader.biWidth = buffer->width; |
|
|
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) { |
|
|
int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int commandShow) { |
|
|
LARGE_INTEGER performanceFrequencyResult; |
|
|
LARGE_INTEGER performanceFrequencyResult; |
|
|
QueryPerformanceFrequency(&performanceFrequencyResult); |
|
|
QueryPerformanceFrequency(&performanceFrequencyResult); |
|
|
int64 performanceFrequency = performanceFrequencyResult.QuadPart; |
|
|
|
|
|
|
|
|
globalPerfCountFrequency = performanceFrequencyResult.QuadPart; |
|
|
|
|
|
|
|
|
|
|
|
UINT desiredSchedulerMs = 1; |
|
|
|
|
|
bool32 sleepIsGranular = TIMERR_NOERROR == timeBeginPeriod(desiredSchedulerMs); |
|
|
|
|
|
|
|
|
WNDCLASSA windowClass = {}; |
|
|
WNDCLASSA windowClass = {}; |
|
|
windowClass.style = CS_VREDRAW | CS_HREDRAW; |
|
|
windowClass.style = CS_VREDRAW | CS_HREDRAW; |
|
@@ -364,6 +419,8 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
|
|
|
|
|
|
resizeDIBSection(&globalBackBuffer, 1280, 720); |
|
|
resizeDIBSection(&globalBackBuffer, 1280, 720); |
|
|
|
|
|
|
|
|
|
|
|
real32 targetSecondsPerFrame = 1.0f / (real32)gameUpdateHz; |
|
|
|
|
|
|
|
|
if (RegisterClass(&windowClass)) { |
|
|
if (RegisterClass(&windowClass)) { |
|
|
HWND window = CreateWindowExA( |
|
|
HWND window = CreateWindowExA( |
|
|
NULL, |
|
|
NULL, |
|
@@ -388,6 +445,9 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
GameInput *oldInput = &input[0]; |
|
|
GameInput *oldInput = &input[0]; |
|
|
GameInput *newInput = &input[1]; |
|
|
GameInput *newInput = &input[1]; |
|
|
|
|
|
|
|
|
|
|
|
int debugTimeMarkerIndex = 0; |
|
|
|
|
|
Win32DebugTimeMarker debugMarkers[gameUpdateHz / 2] = {}; |
|
|
|
|
|
|
|
|
win32LoadXInput(); |
|
|
win32LoadXInput(); |
|
|
|
|
|
|
|
|
// sound test |
|
|
// sound test |
|
@@ -396,7 +456,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
soundOutput.runningSampleIndex = 0; |
|
|
soundOutput.runningSampleIndex = 0; |
|
|
soundOutput.bytesPerSample = sizeof(int16)*2; |
|
|
soundOutput.bytesPerSample = sizeof(int16)*2; |
|
|
soundOutput.secondaryBufferSize = soundOutput.samplesPerSecond*soundOutput.bytesPerSample; |
|
|
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); |
|
|
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); |
|
|
win32ClearBuffer(&soundOutput); |
|
|
globalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); |
|
|
globalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); |
|
|
|
|
|
|
|
|
LARGE_INTEGER lastCounter; |
|
|
|
|
|
QueryPerformanceCounter(&lastCounter); |
|
|
|
|
|
|
|
|
LARGE_INTEGER lastWorkCounter = win32GetWallClock(); |
|
|
|
|
|
|
|
|
int64 lastCycleCount = __rdtsc(); |
|
|
int64 lastCycleCount = __rdtsc(); |
|
|
|
|
|
|
|
|
|
|
|
bool32 soundIsValid = false; |
|
|
|
|
|
DWORD lastPlayCursor = 0; |
|
|
|
|
|
|
|
|
while (globalRunning) { |
|
|
while (globalRunning) { |
|
|
GameControllerInput *oldKeyboardController = &oldInput->controllers[0]; |
|
|
GameControllerInput *oldKeyboardController = &oldInput->controllers[0]; |
|
|
GameControllerInput *newKeyboardController = &newInput->controllers[0]; |
|
|
GameControllerInput *newKeyboardController = &newInput->controllers[0]; |
|
@@ -499,15 +563,12 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
videoBuffer.height = globalBackBuffer.height; |
|
|
videoBuffer.height = globalBackBuffer.height; |
|
|
|
|
|
|
|
|
// Sound test |
|
|
// Sound test |
|
|
DWORD playCursor = 0; |
|
|
|
|
|
DWORD writeCursor = 0; |
|
|
|
|
|
DWORD byteToLock = 0; |
|
|
DWORD byteToLock = 0; |
|
|
DWORD targetCursor = 0; |
|
|
|
|
|
|
|
|
DWORD targetCursor = lastPlayCursor; |
|
|
DWORD bytesToWrite = 0; |
|
|
DWORD bytesToWrite = 0; |
|
|
bool soundIsValid = true; |
|
|
|
|
|
if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) { |
|
|
|
|
|
|
|
|
if (soundIsValid) { |
|
|
byteToLock = (soundOutput.runningSampleIndex*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize; |
|
|
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) { |
|
|
if (byteToLock == targetCursor) { |
|
|
bytesToWrite = 0; |
|
|
bytesToWrite = 0; |
|
|
} else if (byteToLock > targetCursor) { |
|
|
} else if (byteToLock > targetCursor) { |
|
@@ -515,7 +576,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
} else { |
|
|
} else { |
|
|
bytesToWrite = targetCursor - byteToLock; |
|
|
bytesToWrite = targetCursor - byteToLock; |
|
|
} |
|
|
} |
|
|
soundIsValid = true; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
GameSoundOutputBuffer soundBuffer = {}; |
|
|
GameSoundOutputBuffer soundBuffer = {}; |
|
@@ -524,23 +584,66 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
soundBuffer.samples = samples; |
|
|
soundBuffer.samples = samples; |
|
|
|
|
|
|
|
|
gameUpdateAndRender(&gameMemory, &videoBuffer, newInput, &soundBuffer); |
|
|
gameUpdateAndRender(&gameMemory, &videoBuffer, newInput, &soundBuffer); |
|
|
|
|
|
|
|
|
if (soundIsValid) { |
|
|
if (soundIsValid) { |
|
|
win32FillSoundBuffer(&soundOutput, byteToLock, bytesToWrite, &soundBuffer); |
|
|
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); |
|
|
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 endCycleCount = __rdtsc(); |
|
|
int64 cyclesElapsed = endCycleCount - lastCycleCount; |
|
|
int64 cyclesElapsed = endCycleCount - lastCycleCount; |
|
|
lastCycleCount = endCycleCount; |
|
|
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; |
|
|
GameInput *temp = newInput; |
|
|
newInput = oldInput; |
|
|
newInput = oldInput; |
|
@@ -552,6 +655,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin |
|
|
} else { |
|
|
} else { |
|
|
// failed |
|
|
// failed |
|
|
} |
|
|
} |
|
|
return(0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|