aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md17
-rwxr-xr-xcode/build.bat19
m---------code/libs/linuxhmh0
-rwxr-xr-xcode/win32_handmade.cpp1580
-rwxr-xr-xcode/win32_handmade.h89
-rw-r--r--output.gifbin176581 -> 0 bytes
-rw-r--r--showcase.mp4bin0 -> 106665 bytes
7 files changed, 1704 insertions, 1 deletions
diff --git a/README.md b/README.md
index 4b78030..db435fe 100644
--- a/README.md
+++ b/README.md
@@ -4,5 +4,20 @@
`Wordled` is a GUI application that lets you draw patterns such that you can later input them in the [Wordle](https://www.nytimes.com/games/wordle/index.html) game. It will then generate the guesses to achieve the required pattern.
## Showcase
+<video autoplay loop muted src="https://git.spacehb.net/wordled/plain/showcase.mp4?h=main">
-
+## Build & Run
+Both following build steps will output executable programs in the `build` directory. Do note that
+this repository already has prebuilt binaries.
+### On Windows
+In the `code` directory run.
+```bat
+.\build.bat
+```
+Afterwards from the `build` directory you can run the `win32_handmade.exe` executable.
+### On Linux
+In the `code` directory run.
+```bat
+./build.sh
+```
+Afterwards from the `build` directory you can run the `linux_handmade` executable.
diff --git a/code/build.bat b/code/build.bat
new file mode 100755
index 0000000..2f9ac95
--- /dev/null
+++ b/code/build.bat
@@ -0,0 +1,19 @@
+@echo off
+
+call cache.bat
+
+set CommonCompilerFlags=-MTd -Gm- -nologo -GR- -EHa- -Oi -WX -W4 -wd4459 -wd4456 -wd4201 -wd4100 -wd4189 -wd4505 -wd4996 -wd4389 -DHANDMADE_SLOW=1 -DHANDMADE_INTERNAL=1 -FC -Z7
+set CommonLinkerFlags=-opt:ref -incremental:no user32.lib Gdi32.lib winmm.lib
+
+IF NOT EXIST ..\build mkdir ..\build
+pushd ..\build
+
+REM 32-Bit
+REM cl %CommonCompilerFlags% ..\code\win32_handmade.cpp /link -subsystem:windows,5.1 %CommonLinkerFlags%
+
+REM 64-Bit
+cl %CommonCompilerFlags% ..\code\handmade.cpp -Fmhandmade.map -LD /link /DLL /EXPORT:GameGetSoundSamples /EXPORT:GameUpdateAndRender
+
+cl %CommonCompilerFlags% ..\code\win32_handmade.cpp -Fmwin32_handmade.map /link %CommonLinkerFlags%
+
+popd \ No newline at end of file
diff --git a/code/libs/linuxhmh b/code/libs/linuxhmh
-Subproject 7f5300d93eceb05e0dc5c666081d5644cbec495
+Subproject 54f819bb81feb686f93a736139bc24639f4efd9
diff --git a/code/win32_handmade.cpp b/code/win32_handmade.cpp
new file mode 100755
index 0000000..0474848
--- /dev/null
+++ b/code/win32_handmade.cpp
@@ -0,0 +1,1580 @@
+/* ========================================================================
+ $File: $
+ $Date: $
+ $Revision: $
+ $Creator: Casey Muratori $
+ $Notice: (C) Copyright 2014 by Molly Rocket, Inc. All Rights Reserved. $
+ ======================================================================== */
+
+/*
+ TODO(casey): THIS IS NOT A FINAL PLATFORM LAYER!!!
+
+ - Saved game locations
+ - Getting a handle to our own executable file
+ - Asset loading path
+ - Threading (launch a thread)
+ - Raw Input (support for multiple keyboards)
+ - Sleep/timeBeginPeriod
+ - ClipCursor() (for multimonitor support)
+ - Fullscreen support
+ - WM_SETCURSOR (control cursor visibility)
+ - QueryCancelAutoplay
+ - WM_ACTIVATEAPP (for when we are not the active application)
+ - Blit speed improvements (BitBlt)
+ - Hardware acceleration (OpenGL or Direct3D or BOTH??)
+ - GetKeyboardLayout (for French keyboards, international WASD support)
+
+ Just a partial list of stuff!!
+*/
+
+#include "handmade.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <xinput.h>
+#include <dsound.h>
+
+#include "win32_handmade.h"
+
+// TODO(casey): This is a global for now.
+global_variable b32 GlobalRunning;
+global_variable b32 GlobalPause;
+global_variable win32_offscreen_buffer GlobalBackbuffer;
+global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer;
+global_variable s64 GlobalPerfCountFrequency;
+
+// NOTE(casey): XInputGetState
+#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
+typedef X_INPUT_GET_STATE(x_input_get_state);
+X_INPUT_GET_STATE(XInputGetStateStub)
+{
+ return(ERROR_DEVICE_NOT_CONNECTED);
+}
+global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
+#define XInputGetState XInputGetState_
+
+// NOTE(casey): XInputSetState
+#define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
+typedef X_INPUT_SET_STATE(x_input_set_state);
+X_INPUT_SET_STATE(XInputSetStateStub)
+{
+ return(ERROR_DEVICE_NOT_CONNECTED);
+}
+global_variable x_input_set_state *XInputSetState_ = XInputSetStateStub;
+#define XInputSetState XInputSetState_
+
+#define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter)
+typedef DIRECT_SOUND_CREATE(direct_sound_create);
+
+internal void
+CatStrings(size_t SourceACount, char *SourceA,
+ size_t SourceBCount, char *SourceB,
+ size_t DestCount, char *Dest)
+{
+ // TODO(casey): Dest bounds checking!
+
+ for(int Index = 0;
+ Index < SourceACount;
+ ++Index)
+ {
+ *Dest++ = *SourceA++;
+ }
+
+ for(int Index = 0;
+ Index < SourceBCount;
+ ++Index)
+ {
+ *Dest++ = *SourceB++;
+ }
+
+ *Dest++ = 0;
+}
+
+internal void
+Win32GetEXEFileName(win32_state *State)
+{
+ // NOTE(casey): Never use MAX_PATH in code that is user-facing, because it
+ // can be dangerous and lead to bad results.
+ DWORD SizeOfFilename = GetModuleFileNameA(0, State->EXEFileName, sizeof(State->EXEFileName));
+ State->OnePastLastEXEFileNameSlash = State->EXEFileName;
+ for(char *Scan = State->EXEFileName;
+ *Scan;
+ ++Scan)
+ {
+ if(*Scan == '\\')
+ {
+ State->OnePastLastEXEFileNameSlash = Scan + 1;
+ }
+ }
+}
+
+internal int
+StringLength(char *String)
+{
+ int Count = 0;
+ while(*String++)
+ {
+ ++Count;
+ }
+ return(Count);
+}
+
+internal void
+Win32BuildEXEPathFileName(win32_state *State, char *FileName,
+ int DestCount, char *Dest)
+{
+ CatStrings(State->OnePastLastEXEFileNameSlash - State->EXEFileName, State->EXEFileName,
+ StringLength(FileName), FileName,
+ DestCount, Dest);
+}
+
+DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUGPlatformFreeFileMemory)
+{
+ if(Memory)
+ {
+ VirtualFree(Memory, 0, MEM_RELEASE);
+ }
+}
+
+DEBUG_PLATFORM_READ_ENTIRE_FILE(DEBUGPlatformReadEntireFile)
+{
+ debug_read_file_result Result = {};
+
+ HANDLE FileHandle = CreateFileA(FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+ if(FileHandle != INVALID_HANDLE_VALUE)
+ {
+ LARGE_INTEGER FileSize;
+ if(GetFileSizeEx(FileHandle, &FileSize))
+ {
+ u32 FileSize32 = SafeTruncateUInt64(FileSize.QuadPart);
+ Result.Contents = VirtualAlloc(0, FileSize32, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
+ if(Result.Contents)
+ {
+ DWORD BytesRead;
+ if(ReadFile(FileHandle, Result.Contents, FileSize32, &BytesRead, 0) &&
+ (FileSize32 == BytesRead))
+ {
+ // NOTE(casey): File read successfully
+ Result.ContentsSize = FileSize32;
+ }
+ else
+ {
+ // TODO(casey): Logging
+ DEBUGPlatformFreeFileMemory(Thread, Result.Contents, Result.ContentsSize);
+ Result.Contents = 0;
+ }
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+
+ CloseHandle(FileHandle);
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+
+ return(Result);
+}
+
+DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DEBUGPlatformWriteEntireFile)
+{
+ b32 Result = false;
+
+ HANDLE FileHandle = CreateFileA(FileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
+ if(FileHandle != INVALID_HANDLE_VALUE)
+ {
+ DWORD BytesWritten;
+ if(WriteFile(FileHandle, Memory, MemorySize, &BytesWritten, 0))
+ {
+ // NOTE(casey): File read successfully
+ Result = (BytesWritten == MemorySize);
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+
+ CloseHandle(FileHandle);
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+
+ return(Result);
+}
+
+inline FILETIME
+Win32GetLastWriteTime(char *Filename)
+{
+ FILETIME LastWriteTime = {};
+
+ WIN32_FILE_ATTRIBUTE_DATA Data;
+ if(GetFileAttributesEx(Filename, GetFileExInfoStandard, &Data))
+ {
+ LastWriteTime = Data.ftLastWriteTime;
+ }
+
+ return(LastWriteTime);
+}
+
+internal win32_game_code
+Win32LoadGameCode(char *SourceDLLName, char *TempDLLName)
+{
+ win32_game_code Result = {};
+
+ // TODO(casey): Need to get the proper path here!
+ // TODO(casey): Automatic determination of when updates are necessary.
+
+ Result.DLLLastWriteTime = Win32GetLastWriteTime(SourceDLLName);
+
+ CopyFile(SourceDLLName, TempDLLName, FALSE);
+
+ Result.GameCodeDLL = LoadLibraryA(TempDLLName);
+ if(Result.GameCodeDLL)
+ {
+ 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(!Result.IsValid)
+ {
+ Result.UpdateAndRender = 0;
+ Result.GetSoundSamples = 0;
+ }
+
+ return(Result);
+}
+
+internal void
+Win32UnloadGameCode(win32_game_code *GameCode)
+{
+ if(GameCode->GameCodeDLL)
+ {
+ FreeLibrary(GameCode->GameCodeDLL);
+ GameCode->GameCodeDLL = 0;
+ }
+
+ GameCode->IsValid = false;
+ GameCode->UpdateAndRender = 0;
+ GameCode->GetSoundSamples = 0;
+}
+
+internal void
+Win32LoadXInput(void)
+{
+ // TODO(casey): Test this on Windows 8
+ HMODULE XInputLibrary = LoadLibraryA("xinput1_4.dll");
+ if(!XInputLibrary)
+ {
+ // TODO(casey): Diagnostic
+ XInputLibrary = LoadLibraryA("xinput9_1_0.dll");
+ }
+
+ if(!XInputLibrary)
+ {
+ // TODO(casey): Diagnostic
+ XInputLibrary = LoadLibraryA("xinput1_3.dll");
+ }
+
+ if(XInputLibrary)
+ {
+ XInputGetState = (x_input_get_state *)GetProcAddress(XInputLibrary, "XInputGetState");
+ if(!XInputGetState) {XInputGetState = XInputGetStateStub;}
+
+ XInputSetState = (x_input_set_state *)GetProcAddress(XInputLibrary, "XInputSetState");
+ if(!XInputSetState) {XInputSetState = XInputSetStateStub;}
+
+ // TODO(casey): Diagnostic
+
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+}
+
+internal void
+Win32InitDSound(HWND Window, s32 SamplesPerSecond, s32 BufferSize)
+{
+ // NOTE(casey): Load the library
+ HMODULE DSoundLibrary = LoadLibraryA("dsound.dll");
+ if(DSoundLibrary)
+ {
+ // NOTE(casey): Get a DirectSound object! - cooperative
+ direct_sound_create *DirectSoundCreate = (direct_sound_create *)
+ GetProcAddress(DSoundLibrary, "DirectSoundCreate");
+
+ // TODO(casey): Double-check that this works on XP - DirectSound8 or 7??
+ LPDIRECTSOUND DirectSound;
+ if(DirectSoundCreate && SUCCEEDED(DirectSoundCreate(0, &DirectSound, 0)))
+ {
+ 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;
+
+ if(SUCCEEDED(DirectSound->SetCooperativeLevel(Window, DSSCL_PRIORITY)))
+ {
+ DSBUFFERDESC BufferDescription = {};
+ BufferDescription.dwSize = sizeof(BufferDescription);
+ BufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
+
+ // NOTE(casey): "Create" a primary buffer
+ // TODO(casey): DSBCAPS_GLOBALFOCUS?
+ LPDIRECTSOUNDBUFFER PrimaryBuffer;
+ if(SUCCEEDED(DirectSound->CreateSoundBuffer(&BufferDescription, &PrimaryBuffer, 0)))
+ {
+ HRESULT Error = PrimaryBuffer->SetFormat(&WaveFormat);
+ if(SUCCEEDED(Error))
+ {
+ // NOTE(casey): We have finally set the format!
+ OutputDebugStringA("Primary buffer format was set.\n");
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+
+ // TODO(casey): DSBCAPS_GETCURRENTPOSITION2
+ DSBUFFERDESC BufferDescription = {};
+ BufferDescription.dwSize = sizeof(BufferDescription);
+ BufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
+ BufferDescription.dwBufferBytes = BufferSize;
+ BufferDescription.lpwfxFormat = &WaveFormat;
+ HRESULT Error = DirectSound->CreateSoundBuffer(&BufferDescription, &GlobalSecondaryBuffer, 0);
+ if(SUCCEEDED(Error))
+ {
+ OutputDebugStringA("Secondary buffer created successfully.\n");
+ }
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+}
+
+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);
+}
+
+internal void
+Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int Width, int Height)
+{
+ // TODO(casey): Bulletproof this.
+ // Maybe don't free first, free after, then free first if that fails.
+
+ if(Buffer->Memory)
+ {
+ VirtualFree(Buffer->Memory, 0, MEM_RELEASE);
+ }
+
+ Buffer->Width = Width;
+ Buffer->Height = Height;
+
+ int BytesPerPixel = 4;
+ Buffer->BytesPerPixel = BytesPerPixel;
+
+ // NOTE(casey): When the biHeight field is negative, this is the clue to
+ // Windows to treat this bitmap as top-down, not bottom-up, meaning that
+ // the first three bytes of the image are the color for the top left pixel
+ // in the bitmap, not the bottom left!
+ Buffer->Info.bmiHeader.biSize = sizeof(Buffer->Info.bmiHeader);
+ Buffer->Info.bmiHeader.biWidth = Buffer->Width;
+ Buffer->Info.bmiHeader.biHeight = -Buffer->Height;
+ Buffer->Info.bmiHeader.biPlanes = 1;
+ Buffer->Info.bmiHeader.biBitCount = 32;
+ Buffer->Info.bmiHeader.biCompression = BI_RGB;
+
+ // NOTE(casey): Thank you to Chris Hecker of Spy Party fame
+ // for clarifying the deal with StretchDIBits and BitBlt!
+ // No more DC for us.
+ int BitmapMemorySize = (Buffer->Width*Buffer->Height)*BytesPerPixel;
+ Buffer->Memory = VirtualAlloc(0, BitmapMemorySize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
+ Buffer->Pitch = Width*BytesPerPixel;
+
+ // TODO(casey): Probably clear this to black
+}
+
+internal void
+Win32DisplayBufferInWindow(win32_offscreen_buffer *Buffer,
+ HDC DeviceContext, int WindowWidth, int WindowHeight)
+{
+ // NOTE(casey): For prototyping purposes, we're going to always blit
+ // 1-to-1 pixels to make sure we don't introduce artifacts with
+ // stretching while we are learning to code the renderer!
+ StretchDIBits(DeviceContext,
+ 0, 0, Buffer->Width, Buffer->Height,
+ 0, 0, Buffer->Width, Buffer->Height,
+ Buffer->Memory,
+ &Buffer->Info,
+ DIB_RGB_COLORS, SRCCOPY);
+}
+
+internal LRESULT CALLBACK
+Win32MainWindowCallback(HWND Window,
+ UINT Message,
+ WPARAM WParam,
+ LPARAM LParam)
+{
+ LRESULT Result = 0;
+
+ switch(Message)
+ {
+ case WM_CLOSE:
+ {
+ // TODO(casey): Handle this with a message to the user?
+ GlobalRunning = false;
+ } break;
+
+ case WM_ACTIVATEAPP:
+ {
+#if 0
+ if(WParam == TRUE)
+ {
+ SetLayeredWindowAttributes(Window, RGB(0, 0, 0), 255, LWA_ALPHA);
+ }
+ else
+ {
+ SetLayeredWindowAttributes(Window, RGB(0, 0, 0), 64, LWA_ALPHA);
+ }
+#endif
+ } break;
+
+ case WM_DESTROY:
+ {
+ // TODO(casey): Handle this as an error - recreate window?
+ GlobalRunning = false;
+ } break;
+
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ {
+ Assert(!"Keyboard input came in through a non-dispatch message!");
+ } break;
+
+ case WM_PAINT:
+ {
+ PAINTSTRUCT Paint;
+ HDC DeviceContext = BeginPaint(Window, &Paint);
+ win32_window_dimension Dimension = Win32GetWindowDimension(Window);
+ Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext,
+ Dimension.Width, Dimension.Height);
+ EndPaint(Window, &Paint);
+ } break;
+
+ default:
+ {
+ // OutputDebugStringA("default\n");
+ Result = DefWindowProcA(Window, Message, WParam, LParam);
+ } break;
+ }
+
+ return(Result);
+}
+
+internal void
+Win32ClearBuffer(win32_sound_output *SoundOutput)
+{
+ VOID *Region1;
+ DWORD Region1Size;
+ VOID *Region2;
+ DWORD Region2Size;
+ if(SUCCEEDED(GlobalSecondaryBuffer->Lock(0, SoundOutput->SecondaryBufferSize,
+ &Region1, &Region1Size,
+ &Region2, &Region2Size,
+ 0)))
+ {
+ // TODO(casey): assert that Region1Size/Region2Size is valid
+ u8 *DestSample = (u8 *)Region1;
+ for(DWORD ByteIndex = 0;
+ ByteIndex < Region1Size;
+ ++ByteIndex)
+ {
+ *DestSample++ = 0;
+ }
+
+ DestSample = (u8 *)Region2;
+ for(DWORD ByteIndex = 0;
+ ByteIndex < Region2Size;
+ ++ByteIndex)
+ {
+ *DestSample++ = 0;
+ }
+
+ GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size);
+ }
+}
+
+internal void
+Win32FillSoundBuffer(win32_sound_output *SoundOutput, DWORD ByteToLock, DWORD BytesToWrite,
+ game_sound_output_buffer *SourceBuffer)
+{
+ // TODO(casey): More strenuous test!
+ VOID *Region1;
+ DWORD Region1Size;
+ VOID *Region2;
+ DWORD Region2Size;
+ if(SUCCEEDED(GlobalSecondaryBuffer->Lock(ByteToLock, BytesToWrite,
+ &Region1, &Region1Size,
+ &Region2, &Region2Size,
+ 0)))
+ {
+ // TODO(casey): assert that Region1Size/Region2Size is valid
+
+ // TODO(casey): Collapse these two loops
+ DWORD Region1SampleCount = Region1Size/SoundOutput->BytesPerSample;
+ s16 *DestSample = (s16 *)Region1;
+ s16 *SourceSample = SourceBuffer->Samples;
+ for(DWORD SampleIndex = 0;
+ SampleIndex < Region1SampleCount;
+ ++SampleIndex)
+ {
+ *DestSample++ = *SourceSample++;
+ *DestSample++ = *SourceSample++;
+ ++SoundOutput->RunningSampleIndex;
+ }
+
+ DWORD Region2SampleCount = Region2Size/SoundOutput->BytesPerSample;
+ DestSample = (s16 *)Region2;
+ for(DWORD SampleIndex = 0;
+ SampleIndex < Region2SampleCount;
+ ++SampleIndex)
+ {
+ *DestSample++ = *SourceSample++;
+ *DestSample++ = *SourceSample++;
+ ++SoundOutput->RunningSampleIndex;
+ }
+
+ GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2, Region2Size);
+ }
+}
+
+internal void
+Win32ProcessKeyboardMessage(game_button_state *NewState, b32 IsDown)
+{
+ if(NewState->EndedDown != IsDown)
+ {
+ NewState->EndedDown = IsDown;
+ ++NewState->HalfTransitionCount;
+ }
+}
+
+internal void
+Win32ProcessXInputDigitalButton(DWORD XInputButtonState,
+ game_button_state *OldState, DWORD ButtonBit,
+ game_button_state *NewState)
+{
+ NewState->EndedDown = ((XInputButtonState & ButtonBit) == ButtonBit);
+ NewState->HalfTransitionCount = (OldState->EndedDown != NewState->EndedDown) ? 1 : 0;
+}
+
+internal r32
+Win32ProcessXInputStickValue(SHORT Value, SHORT DeadZoneThreshold)
+{
+ r32 Result = 0;
+
+ if(Value < -DeadZoneThreshold)
+ {
+ Result = (r32)((Value + DeadZoneThreshold) / (32768.0f - DeadZoneThreshold));
+ }
+ else if(Value > DeadZoneThreshold)
+ {
+ Result = (r32)((Value - DeadZoneThreshold) / (32767.0f - DeadZoneThreshold));
+ }
+
+ return(Result);
+}
+
+internal void
+Win32GetInputFileLocation(win32_state *State, b32 InputStream,
+ int SlotIndex, int DestCount, char *Dest)
+{
+ char Temp[64];
+ wsprintf(Temp, "loop_edit_%d_%s.hmi", SlotIndex, InputStream ? "input" : "state");
+ Win32BuildEXEPathFileName(State, Temp, DestCount, Dest);
+}
+
+internal win32_replay_buffer *
+Win32GetReplayBuffer(win32_state *State, int unsigned Index)
+{
+ Assert(Index < ArrayCount(State->ReplayBuffers));
+ win32_replay_buffer *Result = &State->ReplayBuffers[Index];
+ return(Result);
+}
+
+internal void
+Win32BeginRecordingInput(win32_state *State, int InputRecordingIndex)
+{
+ win32_replay_buffer *ReplayBuffer = Win32GetReplayBuffer(State, InputRecordingIndex);
+ if(ReplayBuffer->MemoryBlock)
+ {
+ State->InputRecordingIndex = InputRecordingIndex;
+
+ char FileName[WIN32_STATE_FILE_NAME_COUNT];
+ Win32GetInputFileLocation(State, true, InputRecordingIndex, sizeof(FileName), FileName);
+ State->RecordingHandle = CreateFileA(FileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
+
+#if 0
+ LARGE_INTEGER FilePosition;
+ FilePosition.QuadPart = State->TotalSize;
+ SetFilePointerEx(State->RecordingHandle, FilePosition, 0, FILE_BEGIN);
+#endif
+
+ CopyMemory(ReplayBuffer->MemoryBlock, State->GameMemoryBlock, State->TotalSize);
+ }
+}
+
+internal void
+Win32EndRecordingInput(win32_state *State)
+{
+ CloseHandle(State->RecordingHandle);
+ State->InputRecordingIndex = 0;
+}
+
+internal void
+Win32BeginInputPlayBack(win32_state *State, int InputPlayingIndex)
+{
+ win32_replay_buffer *ReplayBuffer = Win32GetReplayBuffer(State, InputPlayingIndex);
+ if(ReplayBuffer->MemoryBlock)
+ {
+ State->InputPlayingIndex = InputPlayingIndex;
+
+ char FileName[WIN32_STATE_FILE_NAME_COUNT];
+ Win32GetInputFileLocation(State, true, InputPlayingIndex, sizeof(FileName), FileName);
+ State->PlaybackHandle = CreateFileA(FileName, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
+
+#if 0
+ LARGE_INTEGER FilePosition;
+ FilePosition.QuadPart = State->TotalSize;
+ SetFilePointerEx(State->PlaybackHandle, FilePosition, 0, FILE_BEGIN);
+#endif
+
+ CopyMemory(State->GameMemoryBlock, ReplayBuffer->MemoryBlock, State->TotalSize);
+ }
+}
+
+internal void
+Win32EndInputPlayBack(win32_state *State)
+{
+ CloseHandle(State->PlaybackHandle);
+ State->InputPlayingIndex = 0;
+}
+
+internal void
+Win32RecordInput(win32_state *State, game_input *NewInput)
+{
+ DWORD BytesWritten;
+ WriteFile(State->RecordingHandle, NewInput, sizeof(*NewInput), &BytesWritten, 0);
+}
+
+internal void
+Win32PlayBackInput(win32_state *State, game_input *NewInput)
+{
+ DWORD BytesRead = 0;
+ if(ReadFile(State->PlaybackHandle, NewInput, sizeof(*NewInput), &BytesRead, 0))
+ {
+ if(BytesRead == 0)
+ {
+ // NOTE(casey): We've hit the end of the stream, go back to the beginning
+ int PlayingIndex = State->InputPlayingIndex;
+ Win32EndInputPlayBack(State);
+ Win32BeginInputPlayBack(State, PlayingIndex);
+ ReadFile(State->PlaybackHandle, NewInput, sizeof(*NewInput), &BytesRead, 0);
+ }
+ }
+}
+
+internal void
+Win32ProcessPendingMessages(win32_state *State, game_controller_input *KeyboardController)
+{
+ MSG Message;
+ while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE))
+ {
+ switch(Message.message)
+ {
+ case WM_QUIT:
+ {
+ GlobalRunning = false;
+ } break;
+
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ {
+ u32 VKCode = (u32)Message.wParam;
+
+ // NOTE(casey): Since we are comparing WasDown to IsDown,
+ // we MUST use == and != to convert these bit tests to actual
+ // 0 or 1 values.
+ b32 WasDown = ((Message.lParam & (1 << 30)) != 0);
+ b32 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 == 'R')
+ {
+ Win32ProcessKeyboardMessage(&KeyboardController->MoveDown, IsDown);
+ }
+ else if(VKCode == 'S')
+ {
+ Win32ProcessKeyboardMessage(&KeyboardController->MoveRight, 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_LEFT)
+ {
+ Win32ProcessKeyboardMessage(&KeyboardController->ActionLeft, IsDown);
+ }
+ else if(VKCode == VK_DOWN)
+ {
+ Win32ProcessKeyboardMessage(&KeyboardController->ActionDown, 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;
+ }
+ }
+ else if(VKCode == 'L')
+ {
+ if(IsDown)
+ {
+ if(State->InputPlayingIndex == 0)
+ {
+ if(State->InputRecordingIndex == 0)
+ {
+ Win32BeginRecordingInput(State, 1);
+ }
+ else
+ {
+ Win32EndRecordingInput(State);
+ Win32BeginInputPlayBack(State, 1);
+ }
+ }
+ else
+ {
+ Win32EndInputPlayBack(State);
+ }
+ }
+ }
+#endif
+ }
+
+ b32 AltKeyWasDown = (Message.lParam & (1 << 29));
+ if((VKCode == VK_F4) && AltKeyWasDown)
+ {
+ GlobalRunning = false;
+ }
+ } break;
+
+ default:
+ {
+ TranslateMessage(&Message);
+ DispatchMessageA(&Message);
+ } break;
+ }
+ }
+}
+
+inline LARGE_INTEGER
+Win32GetWallClock(void)
+{
+ LARGE_INTEGER Result;
+ QueryPerformanceCounter(&Result);
+ return(Result);
+}
+
+inline r32
+Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End)
+{
+ r32 Result = ((r32)(End.QuadPart - Start.QuadPart) /
+ (r32)GlobalPerfCountFrequency);
+ return(Result);
+}
+
+#if 0
+
+internal void
+Win32DebugDrawVertical(win32_offscreen_buffer *Backbuffer,
+ int X, int Top, int Bottom, u32 Color)
+{
+ if(Top <= 0)
+ {
+ Top = 0;
+ }
+
+ if(Bottom > Backbuffer->Height)
+ {
+ Bottom = Backbuffer->Height;
+ }
+
+ if((X >= 0) && (X < Backbuffer->Width))
+ {
+ u8 *Pixel = ((u8 *)Backbuffer->Memory +
+ X*Backbuffer->BytesPerPixel +
+ Top*Backbuffer->Pitch);
+ for(int Y = Top;
+ Y < Bottom;
+ ++Y)
+ {
+ *(u32 *)Pixel = Color;
+ Pixel += Backbuffer->Pitch;
+ }
+ }
+}
+
+inline void
+Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer,
+ win32_sound_output *SoundOutput,
+ r32 C, int PadX, int Top, int Bottom,
+ DWORD Value, u32 Color)
+{
+ r32 XReal32 = (C * (r32)Value);
+ int X = PadX + (int)XReal32;
+ Win32DebugDrawVertical(Backbuffer, X, Top, Bottom, Color);
+}
+
+internal void
+Win32DebugSyncDisplay(win32_offscreen_buffer *Backbuffer,
+ int MarkerCount, win32_debug_time_marker *Markers,
+ int CurrentMarkerIndex,
+ win32_sound_output *SoundOutput, r32 TargetSecondsPerFrame)
+{
+ int PadX = 16;
+ int PadY = 16;
+
+ int LineHeight = 64;
+
+ r32 C = (r32)(Backbuffer->Width - 2*PadX) / (r32)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);
+
+ DWORD PlayColor = 0xFFFFFFFF;
+ DWORD WriteColor = 0xFFFF0000;
+ DWORD ExpectedFlipColor = 0xFFFFFF00;
+ DWORD PlayWindowColor = 0xFFFF00FF;
+
+ int Top = PadY;
+ int Bottom = PadY + LineHeight;
+ if(MarkerIndex == CurrentMarkerIndex)
+ {
+ Top += LineHeight+PadY;
+ Bottom += LineHeight+PadY;
+
+ int FirstTop = Top;
+
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputPlayCursor, PlayColor);
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputWriteCursor, WriteColor);
+
+ Top += LineHeight+PadY;
+ Bottom += LineHeight+PadY;
+
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputLocation, PlayColor);
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->OutputLocation + ThisMarker->OutputByteCount, WriteColor);
+
+ Top += LineHeight+PadY;
+ Bottom += LineHeight+PadY;
+
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, FirstTop, Bottom, ThisMarker->ExpectedFlipPlayCursor, ExpectedFlipColor);
+ }
+
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->FlipPlayCursor, PlayColor);
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->FlipPlayCursor + 480*SoundOutput->BytesPerSample, PlayWindowColor);
+ Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->FlipWriteCursor, WriteColor);
+ }
+}
+
+#endif
+
+int CALLBACK
+WinMain(HINSTANCE Instance,
+ HINSTANCE PrevInstance,
+ LPSTR CommandLine,
+ int ShowCode)
+{
+ win32_state Win32State = {};
+
+ LARGE_INTEGER PerfCountFrequencyResult;
+ QueryPerformanceFrequency(&PerfCountFrequencyResult);
+ GlobalPerfCountFrequency = PerfCountFrequencyResult.QuadPart;
+
+ Win32GetEXEFileName(&Win32State);
+
+ char SourceGameCodeDLLFullPath[WIN32_STATE_FILE_NAME_COUNT];
+ Win32BuildEXEPathFileName(&Win32State, "handmade.dll",
+ sizeof(SourceGameCodeDLLFullPath), SourceGameCodeDLLFullPath);
+
+ char TempGameCodeDLLFullPath[WIN32_STATE_FILE_NAME_COUNT];
+ Win32BuildEXEPathFileName(&Win32State, "handmade_temp.dll",
+ sizeof(TempGameCodeDLLFullPath), TempGameCodeDLLFullPath);
+
+ // NOTE(casey): Set the Windows scheduler granularity to 1ms
+ // so that our Sleep() can be more granular.
+ UINT DesiredSchedulerMS = 1;
+ b32 SleepIsGranular = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR);
+
+ Win32LoadXInput();
+
+ WNDCLASSA WindowClass = {};
+
+ Win32ResizeDIBSection(&GlobalBackbuffer, 1280, 720);
+
+ WindowClass.style = CS_HREDRAW|CS_VREDRAW;
+ WindowClass.lpfnWndProc = Win32MainWindowCallback;
+ WindowClass.hInstance = Instance;
+ // WindowClass.hIcon;
+ WindowClass.lpszClassName = "HandmadeHeroWindowClass";
+
+ if(RegisterClassA(&WindowClass))
+ {
+ HWND Window =
+ CreateWindowExA(
+ 0, // WS_EX_TOPMOST|WS_EX_LAYERED,
+ WindowClass.lpszClassName,
+ "Handmade Hero",
+ WS_OVERLAPPEDWINDOW|WS_VISIBLE,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ 0,
+ 0,
+ Instance,
+ 0);
+ if(Window)
+ {
+ win32_sound_output SoundOutput = {};
+
+ // TODO(casey): How do we reliably query on this on Windows?
+ int MonitorRefreshHz = 60;
+ HDC RefreshDC = GetDC(Window);
+ int Win32RefreshRate = GetDeviceCaps(RefreshDC, VREFRESH);
+ ReleaseDC(Window, RefreshDC);
+ if(Win32RefreshRate > 1)
+ {
+ MonitorRefreshHz = Win32RefreshRate;
+ }
+ r32 GameUpdateHz = (MonitorRefreshHz / 2.0f);
+ r32 TargetSecondsPerFrame = 1.0f / (r32)GameUpdateHz;
+
+ // TODO(casey): Make this like sixty seconds?
+ SoundOutput.SamplesPerSecond = 48000;
+ SoundOutput.BytesPerSample = sizeof(s16)*2;
+ SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond*SoundOutput.BytesPerSample;
+ // TODO(casey): Actually compute this variance and see
+ // what the lowest reasonable value is.
+ SoundOutput.SafetyBytes = (int)(((r32)SoundOutput.SamplesPerSecond*(r32)SoundOutput.BytesPerSample / GameUpdateHz)/3.0f);
+ Win32InitDSound(Window, SoundOutput.SamplesPerSecond, SoundOutput.SecondaryBufferSize);
+ Win32ClearBuffer(&SoundOutput);
+ GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
+
+ GlobalRunning = true;
+
+#if 0
+ // NOTE(casey): This tests the PlayCursor/WriteCursor update frequency
+ // On the Handmade Hero machine, it was 480 samples.
+ 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
+
+ // TODO(casey): Pool with bitmap VirtualAlloc
+ s16 *Samples = (s16 *)VirtualAlloc(0, SoundOutput.SecondaryBufferSize,
+ MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
+
+
+#if HANDMADE_INTERNAL
+ LPVOID BaseAddress = (LPVOID)Terabytes(2);
+#else
+ LPVOID BaseAddress = 0;
+#endif
+
+ game_memory GameMemory = {};
+ GameMemory.PermanentStorageSize = Megabytes(64);
+ GameMemory.TransientStorageSize = Gigabytes(1);
+ GameMemory.DEBUGPlatformFreeFileMemory = DEBUGPlatformFreeFileMemory;
+ GameMemory.DEBUGPlatformReadEntireFile = DEBUGPlatformReadEntireFile;
+ GameMemory.DEBUGPlatformWriteEntireFile = DEBUGPlatformWriteEntireFile;
+
+
+ // TODO(casey): Handle various memory footprints (USING SYSTEM METRICS)
+ // TODO(casey): Use MEM_LARGE_PAGES and call adjust token
+ // privileges when not on Windows XP?
+ Win32State.TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize;
+ Win32State.GameMemoryBlock = VirtualAlloc(BaseAddress, (size_t)Win32State.TotalSize,
+ MEM_RESERVE|MEM_COMMIT,
+ PAGE_READWRITE);
+ GameMemory.PermanentStorage = Win32State.GameMemoryBlock;
+ GameMemory.TransientStorage = ((u8 *)GameMemory.PermanentStorage +
+ GameMemory.PermanentStorageSize);
+
+ for(int ReplayIndex = 0;
+ ReplayIndex < ArrayCount(Win32State.ReplayBuffers);
+ ++ReplayIndex)
+ {
+ win32_replay_buffer *ReplayBuffer = &Win32State.ReplayBuffers[ReplayIndex];
+
+ // TODO(casey): Recording system still seems to take too long
+ // on record start - find out what Windows is doing and if
+ // we can speed up / defer some of that processing.
+
+ Win32GetInputFileLocation(&Win32State, false, ReplayIndex,
+ sizeof(ReplayBuffer->FileName), ReplayBuffer->FileName);
+
+ ReplayBuffer->FileHandle =
+ CreateFileA(ReplayBuffer->FileName,
+ GENERIC_WRITE|GENERIC_READ, 0, 0, CREATE_ALWAYS, 0, 0);
+
+ LARGE_INTEGER MaxSize;
+ MaxSize.QuadPart = Win32State.TotalSize;
+ ReplayBuffer->MemoryMap = CreateFileMapping(
+ ReplayBuffer->FileHandle, 0, PAGE_READWRITE,
+ MaxSize.HighPart, MaxSize.LowPart, 0);
+
+ ReplayBuffer->MemoryBlock = MapViewOfFile(
+ ReplayBuffer->MemoryMap, FILE_MAP_ALL_ACCESS, 0, 0, Win32State.TotalSize);
+ if(ReplayBuffer->MemoryBlock)
+ {
+ }
+ else
+ {
+ // TODO(casey): Diagnostic
+ }
+ }
+
+ if(Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage)
+ {
+ game_input Input[2] = {};
+ game_input *NewInput = &Input[0];
+ game_input *OldInput = &Input[1];
+
+ LARGE_INTEGER LastCounter = Win32GetWallClock();
+ LARGE_INTEGER FlipWallClock = Win32GetWallClock();
+
+ int DebugTimeMarkerIndex = 0;
+ win32_debug_time_marker DebugTimeMarkers[30] = {0};
+
+ DWORD AudioLatencyBytes = 0;
+ r32 AudioLatencySeconds = 0;
+ b32 SoundIsValid = false;
+
+ win32_game_code Game = Win32LoadGameCode(SourceGameCodeDLLFullPath,
+ TempGameCodeDLLFullPath);
+ u32 LoadCounter = 0;
+
+ s64 LastCycleCount = __rdtsc();
+ while(GlobalRunning)
+ {
+ FILETIME NewDLLWriteTime = Win32GetLastWriteTime(SourceGameCodeDLLFullPath);
+ if(CompareFileTime(&NewDLLWriteTime, &Game.DLLLastWriteTime) != 0)
+ {
+ Win32UnloadGameCode(&Game);
+ Game = Win32LoadGameCode(SourceGameCodeDLLFullPath,
+ TempGameCodeDLLFullPath);
+ LoadCounter = 0;
+ }
+
+ // TODO(casey): Zeroing macro
+ // TODO(casey): We can't zero everything because the up/down state will
+ // be wrong!!!
+ game_controller_input *OldKeyboardController = GetController(OldInput, 0);
+ game_controller_input *NewKeyboardController = GetController(NewInput, 0);
+ *NewKeyboardController = {};
+ NewKeyboardController->IsConnected = true;
+ for(int ButtonIndex = 0;
+ ButtonIndex < ArrayCount(NewKeyboardController->Buttons);
+ ++ButtonIndex)
+ {
+ NewKeyboardController->Buttons[ButtonIndex].EndedDown =
+ OldKeyboardController->Buttons[ButtonIndex].EndedDown;
+ }
+
+ Win32ProcessPendingMessages(&Win32State, NewKeyboardController);
+
+ if(!GlobalPause)
+ {
+ POINT MouseP;
+ GetCursorPos(&MouseP);
+ ScreenToClient(Window, &MouseP);
+ NewInput->MouseX = MouseP.x;
+ NewInput->MouseY = MouseP.y;
+ NewInput->MouseZ = 0; // TODO(casey): Support mousewheel?
+ Win32ProcessKeyboardMessage(&NewInput->MouseButtons[0],
+ GetKeyState(VK_LBUTTON) & (1 << 15));
+ Win32ProcessKeyboardMessage(&NewInput->MouseButtons[1],
+ GetKeyState(VK_MBUTTON) & (1 << 15));
+ Win32ProcessKeyboardMessage(&NewInput->MouseButtons[2],
+ GetKeyState(VK_RBUTTON) & (1 << 15));
+ Win32ProcessKeyboardMessage(&NewInput->MouseButtons[3],
+ GetKeyState(VK_XBUTTON1) & (1 << 15));
+ Win32ProcessKeyboardMessage(&NewInput->MouseButtons[4],
+ GetKeyState(VK_XBUTTON2) & (1 << 15));
+
+ // TODO(casey): Need to not poll disconnected controllers to avoid
+ // xinput frame rate hit on older libraries...
+ // TODO(casey): Should we poll this more frequently
+ DWORD MaxControllerCount = XUSER_MAX_COUNT;
+ if(MaxControllerCount > (ArrayCount(NewInput->Controllers) - 1))
+ {
+ MaxControllerCount = (ArrayCount(NewInput->Controllers) - 1);
+ }
+
+ for (DWORD ControllerIndex = 0;
+ ControllerIndex < MaxControllerCount;
+ ++ControllerIndex)
+ {
+ DWORD OurControllerIndex = ControllerIndex + 1;
+ game_controller_input *OldController = GetController(OldInput, OurControllerIndex);
+ game_controller_input *NewController = GetController(NewInput, OurControllerIndex);
+
+ XINPUT_STATE ControllerState;
+ if(XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS)
+ {
+ NewController->IsConnected = true;
+ NewController->IsAnalog = OldController->IsAnalog;
+
+ // NOTE(casey): This controller is plugged in
+ // TODO(casey): See if ControllerState.dwPacketNumber increments too rapidly
+ XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad;
+
+ // TODO(casey): This is a square deadzone, check XInput to
+ // verify that the deadzone is "round" and show how to do
+ // round deadzone processing.
+ NewController->StickAverageX = Win32ProcessXInputStickValue(
+ Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+ NewController->StickAverageY = Win32ProcessXInputStickValue(
+ Pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+ if((NewController->StickAverageX != 0.0f) ||
+ (NewController->StickAverageY != 0.0f))
+ {
+ NewController->IsAnalog = true;
+ }
+
+ 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;
+ }
+
+ r32 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);
+ }
+ else
+ {
+ // NOTE(casey): The controller is not available
+ NewController->IsConnected = false;
+ }
+ }
+
+ thread_context Thread = {};
+
+ game_offscreen_buffer Buffer = {};
+ Buffer.Memory = GlobalBackbuffer.Memory;
+ Buffer.Width = GlobalBackbuffer.Width;
+ Buffer.Height = GlobalBackbuffer.Height;
+ Buffer.Pitch = GlobalBackbuffer.Pitch;
+ Buffer.BytesPerPixel = GlobalBackbuffer.BytesPerPixel;
+
+ if(Win32State.InputRecordingIndex)
+ {
+ Win32RecordInput(&Win32State, NewInput);
+ }
+
+ if(Win32State.InputPlayingIndex)
+ {
+ Win32PlayBackInput(&Win32State, NewInput);
+ }
+ if(Game.UpdateAndRender)
+ {
+ Game.UpdateAndRender(&Thread, &GameMemory, NewInput, &Buffer);
+ }
+
+ LARGE_INTEGER AudioWallClock = Win32GetWallClock();
+ r32 FromBeginToAudioSeconds = Win32GetSecondsElapsed(FlipWallClock, AudioWallClock);
+
+ DWORD PlayCursor;
+ DWORD WriteCursor;
+ if(GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK)
+ {
+ /* NOTE(casey):
+
+ Here is how sound output computation works.
+
+ We define a safety value that is the number
+ of samples we think our game update loop
+ may vary by (let's say up to 2ms)
+
+ When we wake up to write audio, we will look
+ and see what the play cursor position is and we
+ will forecast ahead where we think the play
+ cursor will be on the next frame boundary.
+
+ We will then look to see if the write cursor is
+ before that by at least our safety value. If
+ it is, the target fill position is that frame
+ boundary plus one frame. This gives us perfect
+ audio sync in the case of a card that has low
+ enough latency.
+
+ If the write cursor is _after_ that safety
+ margin, then we assume we can never sync the
+ audio perfectly, so we will write one frame's
+ worth of audio plus the safety margin's worth
+ of guard samples.
+ */
+ if(!SoundIsValid)
+ {
+ SoundOutput.RunningSampleIndex = WriteCursor / SoundOutput.BytesPerSample;
+ SoundIsValid = true;
+ }
+
+ DWORD ByteToLock = ((SoundOutput.RunningSampleIndex*SoundOutput.BytesPerSample) %
+ SoundOutput.SecondaryBufferSize);
+
+ DWORD ExpectedSoundBytesPerFrame =
+ (int)((r32)(SoundOutput.SamplesPerSecond*SoundOutput.BytesPerSample) /
+ GameUpdateHz);
+ r32 SecondsLeftUntilFlip = (TargetSecondsPerFrame - FromBeginToAudioSeconds);
+ DWORD ExpectedBytesUntilFlip = (DWORD)((SecondsLeftUntilFlip/TargetSecondsPerFrame)*(r32)ExpectedSoundBytesPerFrame);
+
+ DWORD ExpectedFrameBoundaryByte = PlayCursor + ExpectedBytesUntilFlip;
+
+ DWORD SafeWriteCursor = WriteCursor;
+ if(SafeWriteCursor < PlayCursor)
+ {
+ SafeWriteCursor += SoundOutput.SecondaryBufferSize;
+ }
+ Assert(SafeWriteCursor >= PlayCursor);
+ SafeWriteCursor += SoundOutput.SafetyBytes;
+
+ b32 AudioCardIsLowLatency = (SafeWriteCursor < ExpectedFrameBoundaryByte);
+
+ DWORD TargetCursor = 0;
+ if(AudioCardIsLowLatency)
+ {
+ TargetCursor = (ExpectedFrameBoundaryByte + ExpectedSoundBytesPerFrame);
+ }
+ else
+ {
+ TargetCursor = (WriteCursor + ExpectedSoundBytesPerFrame +
+ SoundOutput.SafetyBytes);
+ }
+ TargetCursor = (TargetCursor % SoundOutput.SecondaryBufferSize);
+
+ DWORD BytesToWrite = 0;
+ if(ByteToLock > TargetCursor)
+ {
+ BytesToWrite = (SoundOutput.SecondaryBufferSize - ByteToLock);
+ BytesToWrite += TargetCursor;
+ }
+ else
+ {
+ BytesToWrite = TargetCursor - ByteToLock;
+ }
+
+ game_sound_output_buffer SoundBuffer = {};
+ SoundBuffer.SamplesPerSecond = SoundOutput.SamplesPerSecond;
+ SoundBuffer.SampleCount = BytesToWrite / SoundOutput.BytesPerSample;
+ SoundBuffer.Samples = Samples;
+ if(Game.GetSoundSamples)
+ {
+ Game.GetSoundSamples(&Thread, &GameMemory, &SoundBuffer);
+ }
+
+#if HANDMADE_INTERNAL
+ win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex];
+ Marker->OutputPlayCursor = PlayCursor;
+ Marker->OutputWriteCursor = WriteCursor;
+ Marker->OutputLocation = ByteToLock;
+ Marker->OutputByteCount = BytesToWrite;
+ Marker->ExpectedFlipPlayCursor = ExpectedFrameBoundaryByte;
+
+ DWORD UnwrappedWriteCursor = WriteCursor;
+ if(UnwrappedWriteCursor < PlayCursor)
+ {
+ UnwrappedWriteCursor += SoundOutput.SecondaryBufferSize;
+ }
+ AudioLatencyBytes = UnwrappedWriteCursor - PlayCursor;
+ AudioLatencySeconds =
+ (((r32)AudioLatencyBytes / (r32)SoundOutput.BytesPerSample) /
+ (r32)SoundOutput.SamplesPerSecond);
+
+#if 0
+ char TextBuffer[256];
+ _snprintf_s(TextBuffer, sizeof(TextBuffer),
+ "BTL:%u TC:%u BTW:%u - PC:%u WC:%u DELTA:%u (%fs)\n",
+ ByteToLock, TargetCursor, BytesToWrite,
+ PlayCursor, WriteCursor, AudioLatencyBytes, AudioLatencySeconds);
+ OutputDebugStringA(TextBuffer);
+#endif
+#endif
+ Win32FillSoundBuffer(&SoundOutput, ByteToLock, BytesToWrite, &SoundBuffer);
+ }
+ else
+ {
+ SoundIsValid = false;
+ }
+
+ LARGE_INTEGER WorkCounter = Win32GetWallClock();
+ r32 WorkSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter);
+
+ // TODO(casey): NOT TESTED YET! PROBABLY BUGGY!!!!!
+ r32 SecondsElapsedForFrame = WorkSecondsElapsed;
+ if(SecondsElapsedForFrame < TargetSecondsPerFrame)
+ {
+ if(SleepIsGranular)
+ {
+ DWORD SleepMS = (DWORD)(1000.0f * (TargetSecondsPerFrame -
+ SecondsElapsedForFrame));
+ if(SleepMS > 0)
+ {
+ Sleep(SleepMS);
+ }
+ }
+
+ r32 TestSecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter,
+ Win32GetWallClock());
+ if(TestSecondsElapsedForFrame < TargetSecondsPerFrame)
+ {
+ // TODO(casey): LOG MISSED SLEEP HERE
+ }
+
+ while(SecondsElapsedForFrame < TargetSecondsPerFrame)
+ {
+ SecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter,
+ Win32GetWallClock());
+ }
+ }
+ else
+ {
+ // TODO(casey): MISSED FRAME RATE!
+ // TODO(casey): Logging
+ }
+
+ LARGE_INTEGER EndCounter = Win32GetWallClock();
+ r32 MSPerFrame = 1000.0f*Win32GetSecondsElapsed(LastCounter, EndCounter);
+ LastCounter = EndCounter;
+
+ win32_window_dimension Dimension = Win32GetWindowDimension(Window);
+ HDC DeviceContext = GetDC(Window);
+ Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext,
+ Dimension.Width, Dimension.Height);
+ ReleaseDC(Window, DeviceContext);
+
+ FlipWallClock = Win32GetWallClock();
+#if HANDMADE_INTERNAL
+ // NOTE(casey): This is debug code
+ {
+ DWORD PlayCursor;
+ DWORD WriteCursor;
+ if(GlobalSecondaryBuffer->GetCurrentPosition(&PlayCursor, &WriteCursor) == DS_OK)
+ {
+ Assert(DebugTimeMarkerIndex < ArrayCount(DebugTimeMarkers));
+ win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex];
+ Marker->FlipPlayCursor = PlayCursor;
+ Marker->FlipWriteCursor = WriteCursor;
+ }
+
+ }
+#endif
+
+ game_input *Temp = NewInput;
+ NewInput = OldInput;
+ OldInput = Temp;
+ // TODO(casey): Should I clear these here?
+
+#if 1
+ u64 EndCycleCount = __rdtsc();
+ u64 CyclesElapsed = EndCycleCount - LastCycleCount;
+ LastCycleCount = EndCycleCount;
+
+ r64 FPS = 0.0f;
+ r64 MCPF = ((r64)CyclesElapsed / (1000.0f * 1000.0f));
+
+ char FPSBuffer[256];
+ _snprintf_s(FPSBuffer, sizeof(FPSBuffer),
+ "%.02fms/f, %.02ff/s, %.02fmc/f\n", MSPerFrame, FPS, MCPF);
+ OutputDebugStringA(FPSBuffer);
+#endif
+
+#if HANDMADE_INTERNAL
+ ++DebugTimeMarkerIndex;
+ if(DebugTimeMarkerIndex == ArrayCount(DebugTimeMarkers))
+ {
+ DebugTimeMarkerIndex = 0;
+ }
+#endif
+ }
+ }
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+ }
+ else
+ {
+ // TODO(casey): Logging
+ }
+
+ return(0);
+}
diff --git a/code/win32_handmade.h b/code/win32_handmade.h
new file mode 100755
index 0000000..ba39332
--- /dev/null
+++ b/code/win32_handmade.h
@@ -0,0 +1,89 @@
+#if !defined(WIN32_HANDMADE_H)
+/* ========================================================================
+ $File: $
+ $Date: $
+ $Revision: $
+ $Creator: Casey Muratori $
+ $Notice: (C) Copyright 2014 by Molly Rocket, Inc. All Rights Reserved. $
+ ======================================================================== */
+
+struct win32_offscreen_buffer
+{
+ // NOTE(casey): Pixels are alwasy 32-bits wide, Memory Order BB GG RR XX
+ BITMAPINFO Info;
+ void *Memory;
+ int Width;
+ int Height;
+ int Pitch;
+ int BytesPerPixel;
+};
+
+struct win32_window_dimension
+{
+ int Width;
+ int Height;
+};
+
+struct win32_sound_output
+{
+ int SamplesPerSecond;
+ u32 RunningSampleIndex;
+ int BytesPerSample;
+ DWORD SecondaryBufferSize;
+ DWORD SafetyBytes;
+ r32 tSine;
+ // TODO(casey): Should running sample index be in bytes as well
+ // TODO(casey): Math gets simpler if we add a "bytes per second" field?
+};
+
+struct win32_debug_time_marker
+{
+ DWORD OutputPlayCursor;
+ DWORD OutputWriteCursor;
+ DWORD OutputLocation;
+ DWORD OutputByteCount;
+ DWORD ExpectedFlipPlayCursor;
+
+ DWORD FlipPlayCursor;
+ DWORD FlipWriteCursor;
+};
+
+struct win32_game_code
+{
+ HMODULE GameCodeDLL;
+ FILETIME DLLLastWriteTime;
+
+ // IMPORTANT(casey): Either of the callbacks can be 0! You must
+ // check before calling.
+ game_update_and_render *UpdateAndRender;
+ game_get_sound_samples *GetSoundSamples;
+
+ b32 IsValid;
+};
+
+#define WIN32_STATE_FILE_NAME_COUNT MAX_PATH
+struct win32_replay_buffer
+{
+ HANDLE FileHandle;
+ HANDLE MemoryMap;
+ char FileName[WIN32_STATE_FILE_NAME_COUNT];
+ void *MemoryBlock;
+};
+struct win32_state
+{
+ u64 TotalSize;
+ void *GameMemoryBlock;
+ win32_replay_buffer ReplayBuffers[4];
+
+ HANDLE RecordingHandle;
+ int InputRecordingIndex;
+
+ HANDLE PlaybackHandle;
+ int InputPlayingIndex;
+
+ char EXEFileName[WIN32_STATE_FILE_NAME_COUNT];
+ char *OnePastLastEXEFileNameSlash;
+};
+
+#define WIN32_HANDMADE_H
+#endif
diff --git a/output.gif b/output.gif
deleted file mode 100644
index c52f4c7..0000000
--- a/output.gif
+++ /dev/null
Binary files differ
diff --git a/showcase.mp4 b/showcase.mp4
new file mode 100644
index 0000000..83db2cd
--- /dev/null
+++ b/showcase.mp4
Binary files differ