|
|
|
|
@@ -3,6 +3,7 @@
|
|
|
|
|
#include <Xinput.h>
|
|
|
|
|
#include <dsound.h>
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|