2178 lines
81 KiB
C++
2178 lines
81 KiB
C++
/* ========================================================================
|
|
$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!!!
|
|
|
|
- Make the right calls so Windows doesn't think we're "still loading" for a bit after we actually start
|
|
- Saved game locations
|
|
- Getting a handle to our own executable file
|
|
- Asset loading path
|
|
- Threading (launch a thread)
|
|
- Raw Input (support for multiple keyboards)
|
|
- ClipCursor() (for multimonitor support)
|
|
- 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)
|
|
- ChangeDisplaySettings option if we detect slow fullscreen blit??
|
|
|
|
Just a partial list of stuff!!
|
|
*/
|
|
|
|
#include "handmade_platform.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 bool32 GlobalRunning;
|
|
global_variable bool32 GlobalPause;
|
|
global_variable win32_offscreen_buffer GlobalBackbuffer;
|
|
global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer;
|
|
global_variable int64 GlobalPerfCountFrequency;
|
|
global_variable bool32 DEBUGGlobalShowCursor;
|
|
global_variable WINDOWPLACEMENT GlobalWindowPosition = {sizeof(GlobalWindowPosition)};
|
|
|
|
// 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 void
|
|
Win32BuildEXEPathFileName(win32_state *State, char *FileName,
|
|
int DestCount, char *Dest)
|
|
{
|
|
CatStrings(State->OnePastLastEXEFileNameSlash - State->EXEFileName, State->EXEFileName,
|
|
StringLength(FileName), FileName,
|
|
DestCount, Dest);
|
|
}
|
|
|
|
#if HANDMADE_INTERNAL
|
|
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))
|
|
{
|
|
uint32 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(Result.Contents);
|
|
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)
|
|
{
|
|
bool32 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);
|
|
}
|
|
|
|
DEBUG_PLATFORM_EXECUTE_SYSTEM_COMMAND(DEBUGExecuteSystemCommand)
|
|
{
|
|
debug_executing_process Result = {};
|
|
|
|
STARTUPINFO StartupInfo = {};
|
|
StartupInfo.cb = sizeof(StartupInfo);
|
|
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
|
StartupInfo.wShowWindow = SW_HIDE;
|
|
|
|
PROCESS_INFORMATION ProcessInfo = {};
|
|
if(CreateProcess(Command,
|
|
CommandLine,
|
|
0,
|
|
0,
|
|
FALSE,
|
|
0,
|
|
0,
|
|
Path,
|
|
&StartupInfo,
|
|
&ProcessInfo))
|
|
{
|
|
Assert(sizeof(Result.OSHandle) >= sizeof(ProcessInfo.hProcess));
|
|
*(HANDLE *)&Result.OSHandle = ProcessInfo.hProcess;
|
|
}
|
|
else
|
|
{
|
|
DWORD ErrorCode = GetLastError();
|
|
*(HANDLE *)&Result.OSHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
|
|
DEBUG_PLATFORM_GET_PROCESS_STATE(DEBUGGetProcessState)
|
|
{
|
|
debug_process_state Result = {};
|
|
|
|
HANDLE hProcess = *(HANDLE *)&Process.OSHandle;
|
|
if(hProcess != INVALID_HANDLE_VALUE)
|
|
{
|
|
Result.StartedSuccessfully = true;
|
|
|
|
if(WaitForSingleObject(hProcess, 0) == WAIT_OBJECT_0)
|
|
{
|
|
DWORD ReturnCode = 0;
|
|
GetExitCodeProcess(hProcess, &ReturnCode);
|
|
Result.ReturnCode = ReturnCode;
|
|
CloseHandle(hProcess);
|
|
}
|
|
else
|
|
{
|
|
Result.IsRunning = true;
|
|
}
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
#endif
|
|
|
|
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, char *LockFileName)
|
|
{
|
|
win32_game_code Result = {};
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA Ignored;
|
|
if(!GetFileAttributesEx(LockFileName, GetFileExInfoStandard, &Ignored))
|
|
{
|
|
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.DEBUGFrameEnd = (debug_game_frame_end *)
|
|
GetProcAddress(Result.GameCodeDLL, "DEBUGGameFrameEnd");
|
|
|
|
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, int32 SamplesPerSecond, int32 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
|
|
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): In release mode, should we not specify DSBCAPS_GLOBALFOCUS?
|
|
|
|
// TODO(casey): DSBCAPS_GETCURRENTPOSITION2
|
|
DSBUFFERDESC BufferDescription = {};
|
|
BufferDescription.dwSize = sizeof(BufferDescription);
|
|
BufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
|
|
#if HANDMADE_INTERNAL
|
|
BufferDescription.dwFlags |= DSBCAPS_GLOBALFOCUS;
|
|
#endif
|
|
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.
|
|
Buffer->Pitch = Align16(Width*BytesPerPixel);
|
|
int BitmapMemorySize = (Buffer->Pitch*Buffer->Height);
|
|
Buffer->Memory = VirtualAlloc(0, BitmapMemorySize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
// TODO(casey): Probably clear this to black
|
|
}
|
|
|
|
internal void
|
|
Win32DisplayBufferInWindow(win32_offscreen_buffer *Buffer,
|
|
HDC DeviceContext, int WindowWidth, int WindowHeight)
|
|
{
|
|
// TODO(casey): Centering / black bars?
|
|
|
|
if((WindowWidth >= Buffer->Width*2) &&
|
|
(WindowHeight >= Buffer->Height*2))
|
|
{
|
|
StretchDIBits(DeviceContext,
|
|
0, 0, 2*Buffer->Width, 2*Buffer->Height,
|
|
0, 0, Buffer->Width, Buffer->Height,
|
|
Buffer->Memory,
|
|
&Buffer->Info,
|
|
DIB_RGB_COLORS, SRCCOPY);
|
|
}
|
|
else
|
|
{
|
|
#if 0
|
|
int OffsetX = 10;
|
|
int OffsetY = 10;
|
|
|
|
PatBlt(DeviceContext, 0, 0, WindowWidth, OffsetY, BLACKNESS);
|
|
PatBlt(DeviceContext, 0, OffsetY + Buffer->Height, WindowWidth, WindowHeight, BLACKNESS);
|
|
PatBlt(DeviceContext, 0, 0, OffsetX, WindowHeight, BLACKNESS);
|
|
PatBlt(DeviceContext, OffsetX + Buffer->Width, 0, WindowWidth, WindowHeight, BLACKNESS);
|
|
#else
|
|
int OffsetX = 0;
|
|
int OffsetY = 0;
|
|
#endif
|
|
|
|
// 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,
|
|
OffsetX, OffsetY, 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_SETCURSOR:
|
|
{
|
|
if(DEBUGGlobalShowCursor)
|
|
{
|
|
Result = DefWindowProcA(Window, Message, WParam, LParam);
|
|
}
|
|
else
|
|
{
|
|
SetCursor(0);
|
|
}
|
|
} 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
|
|
uint8 *DestSample = (uint8 *)Region1;
|
|
for(DWORD ByteIndex = 0;
|
|
ByteIndex < Region1Size;
|
|
++ByteIndex)
|
|
{
|
|
*DestSample++ = 0;
|
|
}
|
|
|
|
DestSample = (uint8 *)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;
|
|
int16 *DestSample = (int16 *)Region1;
|
|
int16 *SourceSample = SourceBuffer->Samples;
|
|
for(DWORD SampleIndex = 0;
|
|
SampleIndex < Region1SampleCount;
|
|
++SampleIndex)
|
|
{
|
|
*DestSample++ = *SourceSample++;
|
|
*DestSample++ = *SourceSample++;
|
|
++SoundOutput->RunningSampleIndex;
|
|
}
|
|
|
|
DWORD Region2SampleCount = Region2Size/SoundOutput->BytesPerSample;
|
|
DestSample = (int16 *)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, bool32 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 real32
|
|
Win32ProcessXInputStickValue(SHORT Value, SHORT DeadZoneThreshold)
|
|
{
|
|
real32 Result = 0;
|
|
|
|
if(Value < -DeadZoneThreshold)
|
|
{
|
|
Result = (real32)((Value + DeadZoneThreshold) / (32768.0f - DeadZoneThreshold));
|
|
}
|
|
else if(Value > DeadZoneThreshold)
|
|
{
|
|
Result = (real32)((Value - DeadZoneThreshold) / (32767.0f - DeadZoneThreshold));
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
|
|
internal void
|
|
Win32GetInputFileLocation(win32_state *State, bool32 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 > 0);
|
|
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
|
|
ToggleFullscreen(HWND Window)
|
|
{
|
|
// NOTE(casey): This follows Raymond Chen's prescription
|
|
// for fullscreen toggling, see:
|
|
// http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx
|
|
|
|
DWORD Style = GetWindowLong(Window, GWL_STYLE);
|
|
if(Style & WS_OVERLAPPEDWINDOW)
|
|
{
|
|
MONITORINFO MonitorInfo = {sizeof(MonitorInfo)};
|
|
if(GetWindowPlacement(Window, &GlobalWindowPosition) &&
|
|
GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), &MonitorInfo))
|
|
{
|
|
SetWindowLong(Window, GWL_STYLE, Style & ~WS_OVERLAPPEDWINDOW);
|
|
SetWindowPos(Window, HWND_TOP,
|
|
MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top,
|
|
MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left,
|
|
MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top,
|
|
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetWindowLong(Window, GWL_STYLE, Style | WS_OVERLAPPEDWINDOW);
|
|
SetWindowPlacement(Window, &GlobalWindowPosition);
|
|
SetWindowPos(Window, 0, 0, 0, 0, 0,
|
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
|
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
|
}
|
|
}
|
|
|
|
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:
|
|
{
|
|
uint32 VKCode = (uint32)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.
|
|
bool32 WasDown = ((Message.lParam & (1 << 30)) != 0);
|
|
bool32 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 == 'S')
|
|
{
|
|
Win32ProcessKeyboardMessage(&KeyboardController->MoveDown, IsDown);
|
|
}
|
|
else if(VKCode == 'D')
|
|
{
|
|
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->Back, IsDown);
|
|
}
|
|
else if(VKCode == VK_SPACE)
|
|
{
|
|
Win32ProcessKeyboardMessage(&KeyboardController->Start, 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
|
|
if(IsDown)
|
|
{
|
|
bool32 AltKeyWasDown = (Message.lParam & (1 << 29));
|
|
if((VKCode == VK_F4) && AltKeyWasDown)
|
|
{
|
|
GlobalRunning = false;
|
|
}
|
|
if((VKCode == VK_RETURN) && AltKeyWasDown)
|
|
{
|
|
if(Message.hwnd)
|
|
{
|
|
ToggleFullscreen(Message.hwnd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
default:
|
|
{
|
|
TranslateMessage(&Message);
|
|
DispatchMessageA(&Message);
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline LARGE_INTEGER
|
|
Win32GetWallClock(void)
|
|
{
|
|
LARGE_INTEGER Result;
|
|
QueryPerformanceCounter(&Result);
|
|
return(Result);
|
|
}
|
|
|
|
inline real32
|
|
Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End)
|
|
{
|
|
real32 Result = ((real32)(End.QuadPart - Start.QuadPart) /
|
|
(real32)GlobalPerfCountFrequency);
|
|
return(Result);
|
|
}
|
|
|
|
#if 0
|
|
internal void
|
|
HandleDebugCycleCounters(game_memory *Memory)
|
|
{
|
|
#if HANDMADE_INTERNAL
|
|
OutputDebugStringA("DEBUG CYCLE COUNTS:\n");
|
|
for(int CounterIndex = 0;
|
|
CounterIndex < ArrayCount(Memory->Counters);
|
|
++CounterIndex)
|
|
{
|
|
debug_cycle_counter *Counter = Memory->Counters + CounterIndex;
|
|
|
|
if(Counter->HitCount)
|
|
{
|
|
char TextBuffer[256];
|
|
_snprintf_s(TextBuffer, sizeof(TextBuffer),
|
|
" %d: %I64ucy %uh %I64ucy/h\n",
|
|
CounterIndex,
|
|
Counter->CycleCount,
|
|
Counter->HitCount,
|
|
Counter->CycleCount / Counter->HitCount);
|
|
OutputDebugStringA(TextBuffer);
|
|
Counter->HitCount = 0;
|
|
Counter->CycleCount = 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
internal void
|
|
Win32DebugDrawVertical(win32_offscreen_buffer *Backbuffer,
|
|
int X, int Top, int Bottom, uint32 Color)
|
|
{
|
|
if(Top <= 0)
|
|
{
|
|
Top = 0;
|
|
}
|
|
|
|
if(Bottom > Backbuffer->Height)
|
|
{
|
|
Bottom = Backbuffer->Height;
|
|
}
|
|
|
|
if((X >= 0) && (X < Backbuffer->Width))
|
|
{
|
|
uint8 *Pixel = ((uint8 *)Backbuffer->Memory +
|
|
X*Backbuffer->BytesPerPixel +
|
|
Top*Backbuffer->Pitch);
|
|
for(int Y = Top;
|
|
Y < Bottom;
|
|
++Y)
|
|
{
|
|
*(uint32 *)Pixel = Color;
|
|
Pixel += Backbuffer->Pitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void
|
|
Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer,
|
|
win32_sound_output *SoundOutput,
|
|
real32 C, int PadX, int Top, int Bottom,
|
|
DWORD Value, uint32 Color)
|
|
{
|
|
real32 XReal32 = (C * (real32)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, real32 TargetSecondsPerFrame)
|
|
{
|
|
int PadX = 16;
|
|
int PadY = 16;
|
|
|
|
int LineHeight = 64;
|
|
|
|
real32 C = (real32)(Backbuffer->Width - 2*PadX) / (real32)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
|
|
|
|
struct platform_work_queue_entry
|
|
{
|
|
platform_work_queue_callback *Callback;
|
|
void *Data;
|
|
};
|
|
|
|
struct platform_work_queue
|
|
{
|
|
uint32 volatile CompletionGoal;
|
|
uint32 volatile CompletionCount;
|
|
|
|
uint32 volatile NextEntryToWrite;
|
|
uint32 volatile NextEntryToRead;
|
|
HANDLE SemaphoreHandle;
|
|
|
|
platform_work_queue_entry Entries[256];
|
|
};
|
|
|
|
internal void
|
|
Win32AddEntry(platform_work_queue *Queue, platform_work_queue_callback *Callback, void *Data)
|
|
{
|
|
// TODO(casey): Switch to InterlockedCompareExchange eventually
|
|
// so that any thread can add?
|
|
uint32 NewNextEntryToWrite = (Queue->NextEntryToWrite + 1) % ArrayCount(Queue->Entries);
|
|
Assert(NewNextEntryToWrite != Queue->NextEntryToRead);
|
|
platform_work_queue_entry *Entry = Queue->Entries + Queue->NextEntryToWrite;
|
|
Entry->Callback = Callback;
|
|
Entry->Data = Data;
|
|
++Queue->CompletionGoal;
|
|
_WriteBarrier();
|
|
Queue->NextEntryToWrite = NewNextEntryToWrite;
|
|
ReleaseSemaphore(Queue->SemaphoreHandle, 1, 0);
|
|
}
|
|
|
|
internal bool32
|
|
Win32DoNextWorkQueueEntry(platform_work_queue *Queue)
|
|
{
|
|
bool32 WeShouldSleep = false;
|
|
|
|
uint32 OriginalNextEntryToRead = Queue->NextEntryToRead;
|
|
uint32 NewNextEntryToRead = (OriginalNextEntryToRead + 1) % ArrayCount(Queue->Entries);
|
|
if(OriginalNextEntryToRead != Queue->NextEntryToWrite)
|
|
{
|
|
uint32 Index = InterlockedCompareExchange((LONG volatile *)&Queue->NextEntryToRead,
|
|
NewNextEntryToRead,
|
|
OriginalNextEntryToRead);
|
|
if(Index == OriginalNextEntryToRead)
|
|
{
|
|
platform_work_queue_entry Entry = Queue->Entries[Index];
|
|
Entry.Callback(Queue, Entry.Data);
|
|
InterlockedIncrement((LONG volatile *)&Queue->CompletionCount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WeShouldSleep = true;
|
|
}
|
|
|
|
return(WeShouldSleep);
|
|
}
|
|
|
|
internal void
|
|
Win32CompleteAllWork(platform_work_queue *Queue)
|
|
{
|
|
while(Queue->CompletionGoal != Queue->CompletionCount)
|
|
{
|
|
Win32DoNextWorkQueueEntry(Queue);
|
|
}
|
|
|
|
Queue->CompletionGoal = 0;
|
|
Queue->CompletionCount = 0;
|
|
}
|
|
|
|
DWORD WINAPI
|
|
ThreadProc(LPVOID lpParameter)
|
|
{
|
|
platform_work_queue *Queue = (platform_work_queue *)lpParameter;
|
|
|
|
u32 TestThreadID = GetThreadID();
|
|
Assert(TestThreadID == GetCurrentThreadId());
|
|
|
|
for(;;)
|
|
{
|
|
if(Win32DoNextWorkQueueEntry(Queue))
|
|
{
|
|
WaitForSingleObjectEx(Queue->SemaphoreHandle, INFINITE, FALSE);
|
|
}
|
|
}
|
|
|
|
// return(0);
|
|
}
|
|
|
|
internal PLATFORM_WORK_QUEUE_CALLBACK(DoWorkerWork)
|
|
{
|
|
char Buffer[256];
|
|
wsprintf(Buffer, "Thread %u: %s\n", GetCurrentThreadId(), (char *)Data);
|
|
OutputDebugStringA(Buffer);
|
|
}
|
|
|
|
internal void
|
|
Win32MakeQueue(platform_work_queue *Queue, uint32 ThreadCount)
|
|
{
|
|
Queue->CompletionGoal = 0;
|
|
Queue->CompletionCount = 0;
|
|
|
|
Queue->NextEntryToWrite = 0;
|
|
Queue->NextEntryToRead = 0;
|
|
|
|
uint32 InitialCount = 0;
|
|
Queue->SemaphoreHandle = CreateSemaphoreEx(0,
|
|
InitialCount,
|
|
ThreadCount,
|
|
0, 0, SEMAPHORE_ALL_ACCESS);
|
|
for(uint32 ThreadIndex = 0;
|
|
ThreadIndex < ThreadCount;
|
|
++ThreadIndex)
|
|
{
|
|
DWORD ThreadID;
|
|
HANDLE ThreadHandle = CreateThread(0, 0, ThreadProc, Queue, 0, &ThreadID);
|
|
CloseHandle(ThreadHandle);
|
|
}
|
|
}
|
|
|
|
struct win32_platform_file_handle
|
|
{
|
|
HANDLE Win32Handle;
|
|
};
|
|
|
|
struct win32_platform_file_group
|
|
{
|
|
HANDLE FindHandle;
|
|
WIN32_FIND_DATAW FindData;
|
|
};
|
|
|
|
internal PLATFORM_GET_ALL_FILE_OF_TYPE_BEGIN(Win32GetAllFilesOfTypeBegin)
|
|
{
|
|
platform_file_group Result = {};
|
|
|
|
// TODO(casey): If we want, someday, make an actual arena used by Win32
|
|
win32_platform_file_group *Win32FileGroup = (win32_platform_file_group *)VirtualAlloc(
|
|
0, sizeof(win32_platform_file_group),
|
|
MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
|
Result.Platform = Win32FileGroup;
|
|
|
|
wchar_t *WildCard = L"*.*";
|
|
switch(Type)
|
|
{
|
|
case PlatformFileType_AssetFile:
|
|
{
|
|
WildCard = L"*.hha";
|
|
} break;
|
|
|
|
case PlatformFileType_SavedGameFile:
|
|
{
|
|
WildCard = L"*.hhs";
|
|
} break;
|
|
|
|
InvalidDefaultCase;
|
|
}
|
|
|
|
Result.FileCount = 0;
|
|
|
|
WIN32_FIND_DATAW FindData;
|
|
HANDLE FindHandle = FindFirstFileW(WildCard, &FindData);
|
|
while(FindHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
++Result.FileCount;
|
|
|
|
if(!FindNextFileW(FindHandle, &FindData))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
FindClose(FindHandle);
|
|
|
|
Win32FileGroup->FindHandle = FindFirstFileW(WildCard, &Win32FileGroup->FindData);
|
|
|
|
return(Result);
|
|
}
|
|
|
|
internal PLATFORM_GET_ALL_FILE_OF_TYPE_END(Win32GetAllFilesOfTypeEnd)
|
|
{
|
|
win32_platform_file_group *Win32FileGroup = (win32_platform_file_group *)FileGroup->Platform;
|
|
if(Win32FileGroup)
|
|
{
|
|
FindClose(Win32FileGroup->FindHandle);
|
|
|
|
VirtualFree(Win32FileGroup, 0, MEM_RELEASE);
|
|
}
|
|
}
|
|
|
|
internal PLATFORM_OPEN_FILE(Win32OpenNextFile)
|
|
{
|
|
win32_platform_file_group *Win32FileGroup = (win32_platform_file_group *)FileGroup->Platform;
|
|
platform_file_handle Result = {};
|
|
|
|
if(Win32FileGroup->FindHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
// TODO(casey): If we want, someday, make an actual arena used by Win32
|
|
win32_platform_file_handle *Win32Handle = (win32_platform_file_handle *)VirtualAlloc(
|
|
0, sizeof(win32_platform_file_handle),
|
|
MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
|
Result.Platform = Win32Handle;
|
|
|
|
if(Win32Handle)
|
|
{
|
|
wchar_t *FileName = Win32FileGroup->FindData.cFileName;
|
|
Win32Handle->Win32Handle = CreateFileW(FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
|
|
Result.NoErrors = (Win32Handle->Win32Handle != INVALID_HANDLE_VALUE);
|
|
}
|
|
|
|
if(!FindNextFileW(Win32FileGroup->FindHandle, &Win32FileGroup->FindData))
|
|
{
|
|
FindClose(Win32FileGroup->FindHandle);
|
|
Win32FileGroup->FindHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
|
|
internal PLATFORM_FILE_ERROR(Win32FileError)
|
|
{
|
|
#if HANDMADE_INTERNAL
|
|
OutputDebugString("WIN32 FILE ERROR: ");
|
|
OutputDebugString(Message);
|
|
OutputDebugString("\n");
|
|
#endif
|
|
|
|
Handle->NoErrors = false;
|
|
}
|
|
|
|
internal PLATFORM_READ_DATA_FROM_FILE(Win32ReadDataFromFile)
|
|
{
|
|
if(PlatformNoFileErrors(Source))
|
|
{
|
|
win32_platform_file_handle *Handle = (win32_platform_file_handle *)Source->Platform;
|
|
OVERLAPPED Overlapped = {};
|
|
Overlapped.Offset = (u32)((Offset >> 0) & 0xFFFFFFFF);
|
|
Overlapped.OffsetHigh = (u32)((Offset >> 32) & 0xFFFFFFFF);
|
|
|
|
uint32 FileSize32 = SafeTruncateUInt64(Size);
|
|
|
|
DWORD BytesRead;
|
|
if(ReadFile(Handle->Win32Handle, Dest, FileSize32, &BytesRead, &Overlapped) &&
|
|
(FileSize32 == BytesRead))
|
|
{
|
|
// NOTE(casey): File read succeeded!
|
|
}
|
|
else
|
|
{
|
|
Win32FileError(Source, "Read file failed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
internal PLATFORM_FILE_ERROR(Win32CloseFile)
|
|
{
|
|
CloseHandle(FileHandle);
|
|
}
|
|
|
|
*/
|
|
|
|
PLATFORM_ALLOCATE_MEMORY(Win32AllocateMemory)
|
|
{
|
|
void *Result = VirtualAlloc(0, Size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
return(Result);
|
|
}
|
|
|
|
PLATFORM_DEALLOCATE_MEMORY(Win32DeallocateMemory)
|
|
{
|
|
if(Memory)
|
|
{
|
|
VirtualFree(Memory, 0, MEM_RELEASE);
|
|
}
|
|
}
|
|
|
|
#if HANDMADE_INTERNAL
|
|
global_variable debug_table GlobalDebugTable_;
|
|
debug_table *GlobalDebugTable = &GlobalDebugTable_;
|
|
#endif
|
|
|
|
int CALLBACK
|
|
WinMain(HINSTANCE Instance,
|
|
HINSTANCE PrevInstance,
|
|
LPSTR CommandLine,
|
|
int ShowCode)
|
|
{
|
|
win32_state Win32State = {};
|
|
|
|
platform_work_queue HighPriorityQueue = {};
|
|
Win32MakeQueue(&HighPriorityQueue, 6);
|
|
|
|
platform_work_queue LowPriorityQueue = {};
|
|
Win32MakeQueue(&LowPriorityQueue, 2);
|
|
|
|
#if 0
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A0");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A1");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A2");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A3");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A4");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A5");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A6");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A7");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A8");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String A9");
|
|
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B0");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B1");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B2");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B3");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B4");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B5");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B6");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B7");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B8");
|
|
Win32AddEntry(&Queue, DoWorkerWork, "String B9");
|
|
|
|
Win32CompleteAllWork(&Queue);
|
|
#endif
|
|
|
|
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);
|
|
|
|
char GameCodeLockFullPath[WIN32_STATE_FILE_NAME_COUNT];
|
|
Win32BuildEXEPathFileName(&Win32State, "lock.tmp",
|
|
sizeof(GameCodeLockFullPath), GameCodeLockFullPath);
|
|
|
|
// NOTE(casey): Set the Windows scheduler granularity to 1ms
|
|
// so that our Sleep() can be more granular.
|
|
UINT DesiredSchedulerMS = 1;
|
|
bool32 SleepIsGranular = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR);
|
|
|
|
Win32LoadXInput();
|
|
|
|
#if HANDMADE_INTERNAL
|
|
DEBUGGlobalShowCursor = true;
|
|
#endif
|
|
WNDCLASSA WindowClass = {};
|
|
|
|
/* NOTE(casey): 1080p display mode is 1920x1080 -> Half of that is 960x540
|
|
1920 -> 2048 = 2048-1920 -> 128 pixels
|
|
1080 -> 2048 = 2048-1080 -> pixels 968
|
|
1024 + 128 = 1152
|
|
*/
|
|
// Win32ResizeDIBSection(&GlobalBackbuffer, 960, 540);
|
|
Win32ResizeDIBSection(&GlobalBackbuffer, 1920, 1080);
|
|
// Win32ResizeDIBSection(&GlobalBackbuffer, 1279, 719);
|
|
|
|
WindowClass.style = CS_HREDRAW|CS_VREDRAW;
|
|
WindowClass.lpfnWndProc = Win32MainWindowCallback;
|
|
WindowClass.hInstance = Instance;
|
|
WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
|
|
// 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;
|
|
}
|
|
real32 GameUpdateHz = (real32)(MonitorRefreshHz / 2.0f);
|
|
real32 TargetSecondsPerFrame = 1.0f / (real32)GameUpdateHz;
|
|
|
|
// TODO(casey): Make this like sixty seconds?
|
|
SoundOutput.SamplesPerSecond = 48000;
|
|
SoundOutput.BytesPerSample = sizeof(int16)*2;
|
|
SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond*SoundOutput.BytesPerSample;
|
|
// TODO(casey): Actually compute this variance and see
|
|
// what the lowest reasonable value is.
|
|
SoundOutput.SafetyBytes = (int)(((real32)SoundOutput.SamplesPerSecond*(real32)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
|
|
// TODO(casey): Remove MaxPossibleOverrun?
|
|
u32 MaxPossibleOverrun = 2*8*sizeof(u16);
|
|
int16 *Samples = (int16 *)VirtualAlloc(0, SoundOutput.SecondaryBufferSize + MaxPossibleOverrun,
|
|
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(256);
|
|
GameMemory.TransientStorageSize = Gigabytes(1);
|
|
GameMemory.DebugStorageSize = Megabytes(64);
|
|
GameMemory.HighPriorityQueue = &HighPriorityQueue;
|
|
GameMemory.LowPriorityQueue = &LowPriorityQueue;
|
|
GameMemory.PlatformAPI.AddEntry = Win32AddEntry;
|
|
GameMemory.PlatformAPI.CompleteAllWork = Win32CompleteAllWork;
|
|
|
|
GameMemory.PlatformAPI.GetAllFilesOfTypeBegin = Win32GetAllFilesOfTypeBegin;
|
|
GameMemory.PlatformAPI.GetAllFilesOfTypeEnd = Win32GetAllFilesOfTypeEnd;
|
|
GameMemory.PlatformAPI.OpenNextFile = Win32OpenNextFile;
|
|
GameMemory.PlatformAPI.ReadDataFromFile = Win32ReadDataFromFile;
|
|
GameMemory.PlatformAPI.FileError = Win32FileError;
|
|
|
|
GameMemory.PlatformAPI.AllocateMemory = Win32AllocateMemory;
|
|
GameMemory.PlatformAPI.DeallocateMemory = Win32DeallocateMemory;
|
|
|
|
#if HANDMADE_INTERNAL
|
|
GameMemory.PlatformAPI.DEBUGFreeFileMemory = DEBUGPlatformFreeFileMemory;
|
|
GameMemory.PlatformAPI.DEBUGReadEntireFile = DEBUGPlatformReadEntireFile;
|
|
GameMemory.PlatformAPI.DEBUGWriteEntireFile = DEBUGPlatformWriteEntireFile;
|
|
GameMemory.PlatformAPI.DEBUGExecuteSystemCommand = DEBUGExecuteSystemCommand;
|
|
GameMemory.PlatformAPI.DEBUGGetProcessState = DEBUGGetProcessState;
|
|
#endif
|
|
|
|
// 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?
|
|
|
|
// TODO(casey): TransientStorage needs to be broken up
|
|
// into game transient and cache transient, and only the
|
|
// former need be saved for state playback.
|
|
Win32State.TotalSize = (GameMemory.PermanentStorageSize +
|
|
GameMemory.TransientStorageSize +
|
|
GameMemory.DebugStorageSize);
|
|
Win32State.GameMemoryBlock = VirtualAlloc(BaseAddress, (size_t)Win32State.TotalSize,
|
|
MEM_RESERVE|MEM_COMMIT,
|
|
PAGE_READWRITE);
|
|
GameMemory.PermanentStorage = Win32State.GameMemoryBlock;
|
|
GameMemory.TransientStorage = ((uint8 *)GameMemory.PermanentStorage +
|
|
GameMemory.PermanentStorageSize);
|
|
GameMemory.DebugStorage = ((u8 *)GameMemory.TransientStorage +
|
|
GameMemory.TransientStorageSize);
|
|
|
|
for(int ReplayIndex = 1;
|
|
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;
|
|
real32 AudioLatencySeconds = 0;
|
|
bool32 SoundIsValid = false;
|
|
|
|
win32_game_code Game = Win32LoadGameCode(SourceGameCodeDLLFullPath,
|
|
TempGameCodeDLLFullPath,
|
|
GameCodeLockFullPath);
|
|
while(GlobalRunning)
|
|
{
|
|
//
|
|
//
|
|
//
|
|
|
|
BEGIN_BLOCK(ExecutableRefresh);
|
|
NewInput->dtForFrame = TargetSecondsPerFrame;
|
|
|
|
GameMemory.ExecutableReloaded = false;
|
|
FILETIME NewDLLWriteTime = Win32GetLastWriteTime(SourceGameCodeDLLFullPath);
|
|
if(CompareFileTime(&NewDLLWriteTime, &Game.DLLLastWriteTime) != 0)
|
|
{
|
|
Win32CompleteAllWork(&HighPriorityQueue);
|
|
Win32CompleteAllWork(&LowPriorityQueue);
|
|
|
|
#if HANDMADE_INTERNAL
|
|
GlobalDebugTable = &GlobalDebugTable_;
|
|
#endif
|
|
Win32UnloadGameCode(&Game);
|
|
Game = Win32LoadGameCode(SourceGameCodeDLLFullPath,
|
|
TempGameCodeDLLFullPath,
|
|
GameCodeLockFullPath);
|
|
GameMemory.ExecutableReloaded = true;
|
|
}
|
|
END_BLOCK(ExecutableRefresh);
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
BEGIN_BLOCK(InputProcessing);
|
|
|
|
// 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 = (r32)MouseP.x;
|
|
NewInput->MouseY = (r32)((GlobalBackbuffer.Height - 1) - MouseP.y);
|
|
NewInput->MouseZ = 0; // TODO(casey): Support mousewheel?
|
|
|
|
NewInput->ShiftDown = (GetKeyState(VK_SHIFT) & (1 << 15));
|
|
NewInput->AltDown = (GetKeyState(VK_MENU) & (1 << 15));
|
|
NewInput->ControlDown = (GetKeyState(VK_CONTROL) & (1 << 15));
|
|
|
|
DWORD WinButtonID[PlatformMouseButton_Count] =
|
|
{
|
|
VK_LBUTTON,
|
|
VK_MBUTTON,
|
|
VK_RBUTTON,
|
|
VK_XBUTTON1,
|
|
VK_XBUTTON2,
|
|
};
|
|
for(u32 ButtonIndex = 0;
|
|
ButtonIndex < PlatformMouseButton_Count;
|
|
++ButtonIndex)
|
|
{
|
|
NewInput->MouseButtons[ButtonIndex] = OldInput->MouseButtons[ButtonIndex];
|
|
NewInput->MouseButtons[ButtonIndex].HalfTransitionCount = 0;
|
|
Win32ProcessKeyboardMessage(&NewInput->MouseButtons[ButtonIndex],
|
|
GetKeyState(WinButtonID[ButtonIndex]) & (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;
|
|
}
|
|
|
|
real32 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;
|
|
}
|
|
}
|
|
}
|
|
END_BLOCK(InputProcessing);
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
BEGIN_BLOCK(GameUpdate);
|
|
|
|
game_offscreen_buffer Buffer = {};
|
|
Buffer.Memory = GlobalBackbuffer.Memory;
|
|
Buffer.Width = GlobalBackbuffer.Width;
|
|
Buffer.Height = GlobalBackbuffer.Height;
|
|
Buffer.Pitch = GlobalBackbuffer.Pitch;
|
|
if(!GlobalPause)
|
|
{
|
|
if(Win32State.InputRecordingIndex)
|
|
{
|
|
Win32RecordInput(&Win32State, NewInput);
|
|
}
|
|
|
|
if(Win32State.InputPlayingIndex)
|
|
{
|
|
game_input Temp = *NewInput;
|
|
Win32PlayBackInput(&Win32State, NewInput);
|
|
for(u32 MouseButtonIndex = 0;
|
|
MouseButtonIndex < PlatformMouseButton_Count;
|
|
++MouseButtonIndex)
|
|
{
|
|
NewInput->MouseButtons[MouseButtonIndex] = Temp.MouseButtons[MouseButtonIndex];
|
|
}
|
|
NewInput->MouseX = Temp.MouseX;
|
|
NewInput->MouseY = Temp.MouseY;
|
|
NewInput->MouseZ = Temp.MouseZ;
|
|
}
|
|
if(Game.UpdateAndRender)
|
|
{
|
|
Game.UpdateAndRender(&GameMemory, NewInput, &Buffer);
|
|
// HandleDebugCycleCounters(&GameMemory);
|
|
}
|
|
}
|
|
|
|
END_BLOCK(GameUpdate);
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
BEGIN_BLOCK(AudioUpdate);
|
|
|
|
if(!GlobalPause)
|
|
{
|
|
LARGE_INTEGER AudioWallClock = Win32GetWallClock();
|
|
real32 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)((real32)(SoundOutput.SamplesPerSecond*SoundOutput.BytesPerSample) /
|
|
GameUpdateHz);
|
|
real32 SecondsLeftUntilFlip = (TargetSecondsPerFrame - FromBeginToAudioSeconds);
|
|
DWORD ExpectedBytesUntilFlip = (DWORD)((SecondsLeftUntilFlip/TargetSecondsPerFrame)*(real32)ExpectedSoundBytesPerFrame);
|
|
|
|
DWORD ExpectedFrameBoundaryByte = PlayCursor + ExpectedBytesUntilFlip;
|
|
|
|
DWORD SafeWriteCursor = WriteCursor;
|
|
if(SafeWriteCursor < PlayCursor)
|
|
{
|
|
SafeWriteCursor += SoundOutput.SecondaryBufferSize;
|
|
}
|
|
Assert(SafeWriteCursor >= PlayCursor);
|
|
SafeWriteCursor += SoundOutput.SafetyBytes;
|
|
|
|
bool32 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 = Align8(BytesToWrite / SoundOutput.BytesPerSample);
|
|
BytesToWrite = SoundBuffer.SampleCount*SoundOutput.BytesPerSample;
|
|
SoundBuffer.Samples = Samples;
|
|
if(Game.GetSoundSamples)
|
|
{
|
|
Game.GetSoundSamples(&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 =
|
|
(((real32)AudioLatencyBytes / (real32)SoundOutput.BytesPerSample) /
|
|
(real32)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;
|
|
}
|
|
}
|
|
|
|
END_BLOCK(AudioUpdate);
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
#if HANDMADE_INTERNAL
|
|
BEGIN_BLOCK(DebugCollation);
|
|
|
|
if(Game.DEBUGFrameEnd)
|
|
{
|
|
GlobalDebugTable = Game.DEBUGFrameEnd(&GameMemory, NewInput, &Buffer);
|
|
}
|
|
GlobalDebugTable_.EventArrayIndex_EventIndex = 0;
|
|
|
|
END_BLOCK(DebugCollation);
|
|
#endif
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// TODO(casey): Leave this off until we have actual vblank support?
|
|
#if 0
|
|
BEGIN_BLOCK(FramerateWait);
|
|
|
|
if(!GlobalPause)
|
|
{
|
|
LARGE_INTEGER WorkCounter = Win32GetWallClock();
|
|
real32 WorkSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter);
|
|
|
|
// TODO(casey): NOT TESTED YET! PROBABLY BUGGY!!!!!
|
|
real32 SecondsElapsedForFrame = WorkSecondsElapsed;
|
|
if(SecondsElapsedForFrame < TargetSecondsPerFrame)
|
|
{
|
|
if(SleepIsGranular)
|
|
{
|
|
DWORD SleepMS = (DWORD)(1000.0f * (TargetSecondsPerFrame -
|
|
SecondsElapsedForFrame));
|
|
if(SleepMS > 0)
|
|
{
|
|
Sleep(SleepMS);
|
|
}
|
|
}
|
|
|
|
real32 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
|
|
}
|
|
}
|
|
|
|
END_BLOCK(FramerateWait);
|
|
#endif
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
BEGIN_BLOCK(FrameDisplay);
|
|
|
|
win32_window_dimension Dimension = Win32GetWindowDimension(Window);
|
|
HDC DeviceContext = GetDC(Window);
|
|
Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext,
|
|
Dimension.Width, Dimension.Height);
|
|
ReleaseDC(Window, DeviceContext);
|
|
|
|
FlipWallClock = Win32GetWallClock();
|
|
|
|
game_input *Temp = NewInput;
|
|
NewInput = OldInput;
|
|
OldInput = Temp;
|
|
// TODO(casey): Should I clear these here?
|
|
|
|
END_BLOCK(FrameDisplay);
|
|
|
|
LARGE_INTEGER EndCounter = Win32GetWallClock();
|
|
FRAME_MARKER(Win32GetSecondsElapsed(LastCounter, EndCounter));
|
|
LastCounter = EndCounter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO(casey): Logging
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO(casey): Logging
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO(casey): Logging
|
|
}
|
|
|
|
return(0);
|
|
}
|