@@ -1,2 +1,2 @@ | |||
call build || exit /b %errorlevel% | |||
devenv .\build\handmade.exe | |||
devenv .\build\handmade_win32.exe |
@@ -1,2 +1,2 @@ | |||
call build || exit /b %errorlevel% | |||
.\build\handmade.exe | |||
.\build\handmade_win32.exe |
@@ -1,9 +1,9 @@ | |||
@echo off | |||
if NOT EXIST .\build mkdir .\build | |||
pushd .\build | |||
cl ^ | |||
set commonLinkerFlags= -opt:ref user32.lib Gdi32.lib winmm.lib | |||
set commonCompilerFlags=^ | |||
-MT %= Make sure the C runtime library is statically linked =%^ | |||
-Fmwin32_handmade.map %= Create a map file =%^ | |||
-Gm- %= Turns off incremently building =%^ | |||
-nologo %= No one cares you made the compiler Microsoft =%^ | |||
-Oi %= Always use intrinsics =%^ | |||
@@ -12,10 +12,12 @@ cl ^ | |||
-WX -W4 -wd4201 -wd4100 -wd4189 %= Compiler warnings, -WX warnings as errors, -W4 warning level 4, -wdXXXX disable warning XXXX =%^ | |||
-DHANDMADE_INTERNAL=1 -DHANDMADE_SLOW=1 -DHANDMADE_WIN32=1 %= Custom #defines =%^ | |||
-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 winmm.lib %= Linked libraries =%^ | |||
/link -subsystem:windows,5.1 %= Linker stuff =% | |||
-Zi %= Generate debugger info =% | |||
pushd .\build | |||
cl %commonCompilerFlags% -Fe:handmade.dll ..\src\handmade.cpp -Fmhandmade.map /link /DLL /EXPORT:gameGetSoundSamples /EXPORT:gameUpdateAndRender | |||
cl %commonCompilerFlags% -Fe:handmade_win32.exe ..\src\win32_handmade.cpp -Fmwin32_handmade.map /link %commonLinkerFlags% | |||
popd | |||
exit /b | |||
@@ -2,16 +2,6 @@ | |||
#define PI32 3.141592653589f | |||
void debug_printf(wchar_t* format, ...) { | |||
size_t bufsize = wcslen(format)*10; | |||
wchar_t *output = (wchar_t*)malloc(bufsize); | |||
va_list list; | |||
va_start(list, format); | |||
vswprintf(output, bufsize, format, list); | |||
OutputDebugStringW(output); | |||
free(output); | |||
} | |||
internal void outputSineSound(GameSoundOutputBuffer *soundBuffer, GameState *state) { | |||
int16 toneVolume = 3000; | |||
int wavePeriod = soundBuffer->samplesPerSecond/state->toneHz; | |||
@@ -34,23 +24,25 @@ internal void renderWeirdGradient(GameOffscreenBuffer *buffer, int xOffset, int | |||
for (int x = 0; x < buffer->width; x++) { | |||
uint8 blue = (uint8)(x + xOffset); | |||
uint8 green = (uint8)(y + yOffset); | |||
*pixel++ = (green << 8) | blue; | |||
*pixel++ = (green << 16) | blue; | |||
} | |||
row += pitch; | |||
} | |||
} | |||
internal void gameUpdateAndRender(GameMemory *memory, GameOffscreenBuffer *videoBuf, GameInput *input) { | |||
extern "C" GAME_UPDATE_AND_RENDER(gameUpdateAndRender) { | |||
Assert(sizeof(GameState) <= memory->permanentStorageSize); | |||
GameState *state = (GameState*)memory->permanentStorage; | |||
if (!memory->isInitialised) { | |||
//DebugReadFileResult bmpMem = DEBUG_platformReadEntireFile(__FILE__); | |||
//if (bmpMem.contents) { | |||
// DEBUG_platformWriteEntireFile("c:/source/repos/handmade/src/test.cpp", bmpMem.contentsSize, bmpMem.contents); | |||
// DEBUG_platformFreeFileMemory(bmpMem.contents); | |||
//} | |||
DebugReadFileResult bmpMem = memory->debugReadEntireFile(__FILE__); | |||
if (bmpMem.contents) { | |||
if (false) { | |||
memory->debugWriteEntireFile("c:/source/repos/handmade/src/test.cpp", bmpMem.contentsSize, bmpMem.contents); | |||
} | |||
memory->debugFreeFileMemory(bmpMem.contents); | |||
} | |||
state->toneHz = 440; | |||
state->tSine = 0; | |||
@@ -70,8 +62,6 @@ internal void gameUpdateAndRender(GameMemory *memory, GameOffscreenBuffer *video | |||
} else if (controllerInput->stickLeft.endedDown) { | |||
state->toneHz = 440 - 128; | |||
state->greenOffset += 10; | |||
} else { | |||
//state->toneHz = 440; | |||
} | |||
if (controllerInput->stickUp.endedDown) { | |||
state->blueOffset += 10; | |||
@@ -84,6 +74,14 @@ internal void gameUpdateAndRender(GameMemory *memory, GameOffscreenBuffer *video | |||
renderWeirdGradient(videoBuf, state->greenOffset, state->blueOffset); | |||
} | |||
internal void gameGetSoundSamples(GameMemory *memory, GameSoundOutputBuffer *soundBuf) { | |||
extern "C" GAME_GET_SOUND_SAMPLES(gameGetSoundSamples) { | |||
outputSineSound(soundBuf, (GameState*)memory->permanentStorage); | |||
} | |||
#if HANDMADE_WIN32 | |||
#include "windows.h" | |||
BOOL DllMain() { | |||
return TRUE; | |||
} | |||
#endif |
@@ -64,6 +64,28 @@ inline uint32 safeTruncateUInt64(uint64 val) { | |||
return (uint32)val; | |||
} | |||
// === Platform to game services === | |||
#if HANDMADE_INTERNAL | |||
struct DebugReadFileResult { | |||
uint32 contentsSize; | |||
void *contents; | |||
}; | |||
#define DEBUG_PLATFORM_READ_ENTIRE_FILE(name) DebugReadFileResult name(char *filename) | |||
typedef DEBUG_PLATFORM_READ_ENTIRE_FILE(DebugPlatformReadEntireFileFn); | |||
#define DEBUG_PLATFORM_FREE_FILE_MEMORY(name) void name(void *fileMemory) | |||
typedef DEBUG_PLATFORM_FREE_FILE_MEMORY(DebugPlatformFreeFileMemoryFn); | |||
#define DEBUG_PLATFORM_WRITE_ENTIRE_FILE(name) bool32 name(char *filename, uint32 memorySize, void *memory) | |||
typedef DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DebugPlatformWriteEntireFileFn); | |||
#define DEBUG_PLATFORM_PRINTF(name) void name(wchar_t* format, ...) | |||
typedef DEBUG_PLATFORM_PRINTF(DebugPrintfFn); | |||
#endif | |||
// Game to platform layer services | |||
struct GameSoundOutputBuffer { | |||
int samplesPerSecond; | |||
@@ -111,10 +133,17 @@ struct GameInput { | |||
struct GameMemory { | |||
bool32 isInitialised; | |||
uint64 permanentStorageSize; | |||
void *permanentStorage; // required to be initialised to zero at startup | |||
uint64 transientStorageSize; | |||
void *transientStorage; // required to be initialised to zero at startup | |||
DebugPlatformReadEntireFileFn *debugReadEntireFile; | |||
DebugPlatformFreeFileMemoryFn *debugFreeFileMemory; | |||
DebugPlatformWriteEntireFileFn *debugWriteEntireFile; | |||
DebugPrintfFn *debug_printf; | |||
}; | |||
struct GameState { | |||
@@ -125,22 +154,11 @@ struct GameState { | |||
}; | |||
// === Game to platform services === | |||
internal void gameUpdateAndRender(GameMemory *memory, GameInput *input, GameOffscreenBuffer *videoBuf); | |||
internal void gameGetSoundSamples(GameMemory *memory, GameSoundOutputBuffer *soundBuf); | |||
// === Platform to game services === | |||
#if HANDMADE_INTERNAL | |||
struct DebugReadFileResult { | |||
uint32 contentsSize; | |||
void *contents; | |||
}; | |||
DebugReadFileResult DEBUG_platformReadEntireFile(char *filename); | |||
void DEBUG_platformFreeFileMemory(void *memory); | |||
bool32 DEBUG_platformWriteEntireFile(char *filename, uint32 memorySize, void *memory); | |||
void debug_printf(wchar_t* format, ...); | |||
#endif | |||
#define GAME_UPDATE_AND_RENDER(name) void name(GameMemory *memory, GameOffscreenBuffer *videoBuf, GameInput *input) | |||
typedef GAME_UPDATE_AND_RENDER(GameUpdateAndRenderFn); | |||
GAME_UPDATE_AND_RENDER(gameUpdateAndRenderStub) {} | |||
#define GAME_GET_SOUND_SAMPLES(name) void name(GameMemory *memory, GameSoundOutputBuffer *soundBuf) | |||
typedef GAME_GET_SOUND_SAMPLES(GameGetSoundSamplesFn); | |||
GAME_GET_SOUND_SAMPLES(gameGetSoundSamplesStub) {} |
@@ -4,8 +4,8 @@ | |||
#include <dsound.h> | |||
// Local imports | |||
#include "handmade.h" | |||
#include "win32_handmade.h" | |||
#include "handmade.cpp" | |||
global ATOM HH_CTRLW; | |||
global bool globalRunning; | |||
@@ -31,13 +31,23 @@ typedef HRESULT WINAPI DirectSoundCreateFn(LPCGUID pcGuidDevice, LPDIRECTSOUND * | |||
#define stackAlloc(size, type) (type*)_alloca(size*sizeof(type)) | |||
void DEBUG_platformFreeFileMemory(void *fileMemory) { | |||
DEBUG_PLATFORM_PRINTF(debug_printf) { | |||
size_t bufsize = wcslen(format)*10; | |||
wchar_t *output = (wchar_t*)malloc(bufsize); | |||
va_list list; | |||
va_start(list, format); | |||
vswprintf(output, bufsize, format, list); | |||
OutputDebugStringW(output); | |||
free(output); | |||
} | |||
DEBUG_PLATFORM_FREE_FILE_MEMORY(debugFreeFileMemory) { | |||
if (fileMemory) { | |||
VirtualFree(fileMemory, NULL, MEM_RELEASE); | |||
} | |||
} | |||
DebugReadFileResult DEBUG_platformReadEntireFile(char *filename) { | |||
DEBUG_PLATFORM_READ_ENTIRE_FILE(debugReadEntireFile) { | |||
DebugReadFileResult result = {}; | |||
HANDLE fileHandle = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); | |||
if (fileHandle != INVALID_HANDLE_VALUE) { | |||
@@ -50,7 +60,7 @@ DebugReadFileResult DEBUG_platformReadEntireFile(char *filename) { | |||
if (ReadFile(fileHandle, result.contents, (DWORD)fileSize.QuadPart, &bytesRead, NULL) && (fileSize32 == (uint32)bytesRead)) { | |||
result.contentsSize = fileSize32; | |||
} else { | |||
DEBUG_platformFreeFileMemory(result.contents); | |||
debugFreeFileMemory(result.contents); | |||
result.contents = NULL; | |||
// logging | |||
} | |||
@@ -68,7 +78,7 @@ DebugReadFileResult DEBUG_platformReadEntireFile(char *filename) { | |||
return result; | |||
} | |||
bool32 DEBUG_platformWriteEntireFile(char *filename, uint32 memorySize, void *memory) { | |||
DEBUG_PLATFORM_WRITE_ENTIRE_FILE(debugWriteEntireFile) { | |||
bool32 result = false; | |||
HANDLE fileHandle = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, NULL, NULL); | |||
if (fileHandle != INVALID_HANDLE_VALUE) { | |||
@@ -128,6 +138,42 @@ internal void win32DrawBufferInWindow(Win32OffscreenBuffer *buffer, HWND window) | |||
); | |||
} | |||
struct Win32GameCode { | |||
HMODULE gameCodeLib; | |||
GameUpdateAndRenderFn *updateAndRender; | |||
GameGetSoundSamplesFn *getSoundSamples; | |||
bool isValid; | |||
}; | |||
internal Win32GameCode win32LoadGameCode() { | |||
Win32GameCode result = {}; | |||
CopyFile("handmade.dll", "handmade_temp.dll", FALSE); | |||
HMODULE gameCodeLib = LoadLibrary("handmade_temp.dll"); | |||
result.gameCodeLib = gameCodeLib; | |||
if (gameCodeLib) { | |||
result.updateAndRender = (GameUpdateAndRenderFn *)GetProcAddress(result.gameCodeLib, "gameUpdateAndRender"); | |||
result.getSoundSamples = (GameGetSoundSamplesFn *)GetProcAddress(result.gameCodeLib, "gameGetSoundSamples"); | |||
result.isValid = (result.updateAndRender && result.getSoundSamples); | |||
} | |||
if (!result.isValid) { | |||
result.getSoundSamples = gameGetSoundSamplesStub; | |||
result.updateAndRender = gameUpdateAndRenderStub; | |||
} | |||
return result; | |||
} | |||
internal void win32UnloadGameCode(Win32GameCode *gameCode) { | |||
if (gameCode->gameCodeLib) { | |||
FreeLibrary(gameCode->gameCodeLib); | |||
} | |||
gameCode->isValid = false; | |||
gameCode->getSoundSamples = gameGetSoundSamplesStub; | |||
gameCode->updateAndRender = gameUpdateAndRenderStub; | |||
} | |||
internal void win32LoadXInput() { | |||
HMODULE xInputLib = LoadLibrary("xinput1_4.dll"); | |||
if (!xInputLib) { | |||
@@ -472,6 +518,10 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin | |||
GameMemory gameMemory = {}; | |||
gameMemory.permanentStorageSize = Megabytes(64); | |||
gameMemory.transientStorageSize = Gigabytes((uint64)4); | |||
gameMemory.debugFreeFileMemory = debugFreeFileMemory; | |||
gameMemory.debugWriteEntireFile = debugWriteEntireFile; | |||
gameMemory.debugReadEntireFile = debugReadEntireFile; | |||
gameMemory.debug_printf = debug_printf; | |||
uint64 totalSize = gameMemory.permanentStorageSize + gameMemory.transientStorageSize; | |||
gameMemory.permanentStorage = VirtualAlloc(baseAddress, totalSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); | |||
@@ -484,12 +534,23 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin | |||
LARGE_INTEGER lastWorkCounter = win32GetWallClock(); | |||
LARGE_INTEGER flipWallClock = win32GetWallClock(); | |||
int64 lastCycleCount = __rdtsc(); | |||
DWORD audioLatencyBytes = 0; | |||
real32 audioLatencySeconds = 0; | |||
bool soundIsValid = false; | |||
Win32GameCode game = win32LoadGameCode(); | |||
uint32 loadCounter = 0; | |||
int64 lastCycleCount = __rdtsc(); | |||
while (globalRunning) { | |||
if (loadCounter++ > 60) { | |||
win32UnloadGameCode(&game); | |||
game = win32LoadGameCode(); | |||
loadCounter = 0; | |||
// TODO(dledda): handmade hero episode 22 (from the beginning) | |||
} | |||
GameControllerInput *oldKeyboardController = &oldInput->controllers[0]; | |||
GameControllerInput *newKeyboardController = &newInput->controllers[0]; | |||
GameControllerInput zeroController = {}; | |||
@@ -566,11 +627,10 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin | |||
videoBuffer.memory = globalBackBuffer.memory; | |||
videoBuffer.width = globalBackBuffer.width; | |||
videoBuffer.height = globalBackBuffer.height; | |||
gameUpdateAndRender(&gameMemory, &videoBuffer, newInput); | |||
game.updateAndRender(&gameMemory, &videoBuffer, newInput); | |||
DWORD playCursor = 0; | |||
DWORD writeCursor = 0; | |||
bool soundIsValid = false; | |||
if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) { | |||
if (!soundIsValid) { | |||
@@ -579,17 +639,17 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin | |||
} | |||
DWORD byteToLock = (soundOutput.runningSampleIndex*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize; | |||
// TODO(dledda): episode 20 ~2:00, something's wrong though, gotta sort it out. | |||
DWORD expectedSoundBytesPerFrame = (soundOutput.samplesPerSecond * soundOutput.bytesPerSample) / gameUpdateHz; | |||
DWORD expectedFrameBoundaryByte = playCursor + expectedSoundBytesPerFrame; | |||
DWORD safeWriteCursor = writeCursor; | |||
if (safeWriteCursor < playCursor) { | |||
safeWriteCursor += soundOutput.secondaryBufferSize; | |||
} | |||
Assert(safeWriteCursor >= playCursor); | |||
safeWriteCursor += soundOutput.safetyBytes + 2000; | |||
bool audioIsLowLatency = safeWriteCursor >= expectedFrameBoundaryByte; | |||
safeWriteCursor += soundOutput.safetyBytes; | |||
bool audioIsLowLatency = safeWriteCursor < expectedFrameBoundaryByte; | |||
DWORD targetCursor = 0; | |||
if (audioIsLowLatency) { | |||
@@ -610,7 +670,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin | |||
soundBuffer.samplesPerSecond = soundOutput.samplesPerSecond; | |||
soundBuffer.sampleCount = bytesToWrite / soundOutput.bytesPerSample; | |||
soundBuffer.samples = samples; | |||
gameGetSoundSamples(&gameMemory, &soundBuffer); | |||
game.getSoundSamples(&gameMemory, &soundBuffer); | |||
#if HANDMADE_INTERNAL | |||
Assert(debugTimeMarkerIndex < ArrayCount(debugMarkers)); | |||