Last active
September 24, 2023 02:08
-
-
Save mmozeiko/38c64bb65855d783645c to your computer and use it in GitHub Desktop.
win32_handmade.cpp with WASAPI from Day 19
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
THIS IS BAD WAY TO USE WASAPI. DO NOT USE MAYBE EXCEPT AS REFERENCE TO WASAPI FUNCTIONS. | |
/* ======================================================================== | |
$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!! | |
*/ | |
// TODO(casey): Implement sine ourselves | |
#include <math.h> | |
#include <stdint.h> | |
#define internal static | |
#define local_persist static | |
#define global_variable static | |
#define Pi32 3.14159265359f | |
typedef int8_t int8; | |
typedef int16_t int16; | |
typedef int32_t int32; | |
typedef int64_t int64; | |
typedef int32 bool32; | |
typedef uint8_t uint8; | |
typedef uint16_t uint16; | |
typedef uint32_t uint32; | |
typedef uint64_t uint64; | |
typedef float real32; | |
typedef double real64; | |
#include "handmade.h" | |
#include "handmade.cpp" | |
#include <windows.h> | |
#include <stdio.h> | |
#include <malloc.h> | |
#include <xinput.h> | |
#include <mmdeviceapi.h> | |
#include <audioclient.h> | |
#include "win32_handmade.h" | |
// TODO(casey): This is a global for now. | |
global_variable bool32 GlobalRunning; | |
global_variable win32_offscreen_buffer GlobalBackbuffer; | |
global_variable IAudioClient* GlobalSoundClient; | |
global_variable IAudioRenderClient* GlobalSoundRenderClient; | |
global_variable int64 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_ | |
internal debug_read_file_result | |
DEBUGPlatformReadEntireFile(char *Filename) | |
{ | |
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); | |
} | |
internal void | |
DEBUGPlatformFreeFileMemory(void *Memory) | |
{ | |
if(Memory) | |
{ | |
VirtualFree(Memory, 0, MEM_RELEASE); | |
} | |
} | |
internal bool32 | |
DEBUGPlatformWriteEntireFile(char *Filename, uint32 MemorySize, void *Memory) | |
{ | |
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); | |
} | |
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 | |
Win32InitWASAPI(int32 SamplesPerSecond, int32 BufferSizeInSamples) | |
{ | |
if (FAILED(CoInitializeEx(0, COINIT_SPEED_OVER_MEMORY))) | |
{ | |
Assert(!"Error"); | |
} | |
IMMDeviceEnumerator* Enumerator; | |
if (FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&Enumerator)))) | |
{ | |
Assert(!"Error"); | |
} | |
IMMDevice* Device; | |
if (FAILED(Enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &Device))) | |
{ | |
Assert(!"Error"); | |
} | |
if (FAILED(Device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (LPVOID*)&GlobalSoundClient))) | |
{ | |
Assert(!"Error"); | |
} | |
WAVEFORMATEXTENSIBLE WaveFormat; | |
WaveFormat.Format.cbSize = sizeof(WaveFormat); | |
WaveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; | |
WaveFormat.Format.wBitsPerSample = 16; | |
WaveFormat.Format.nChannels = 2; | |
WaveFormat.Format.nSamplesPerSec = (DWORD)SamplesPerSecond; | |
WaveFormat.Format.nBlockAlign = (WORD)(WaveFormat.Format.nChannels * WaveFormat.Format.wBitsPerSample / 8); | |
WaveFormat.Format.nAvgBytesPerSec = WaveFormat.Format.nSamplesPerSec * WaveFormat.Format.nBlockAlign; | |
WaveFormat.Samples.wValidBitsPerSample = 16; | |
WaveFormat.dwChannelMask = KSAUDIO_SPEAKER_STEREO; | |
WaveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; | |
REFERENCE_TIME BufferDuration = 10000000ULL * BufferSizeInSamples / SamplesPerSecond; // buffer size in 100 nanoseconds | |
if (FAILED(GlobalSoundClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_NOPERSIST, BufferDuration, 0, &WaveFormat.Format, nullptr))) | |
{ | |
Assert(!"Error"); | |
} | |
if (FAILED(GlobalSoundClient->GetService(IID_PPV_ARGS(&GlobalSoundRenderClient)))) | |
{ | |
Assert(!"Error"); | |
} | |
UINT32 SoundFrameCount; | |
if (FAILED(GlobalSoundClient->GetBufferSize(&SoundFrameCount))) | |
{ | |
Assert(!"Error"); | |
} | |
// Check if we got what we requested (better would to pass this value back as real buffer size) | |
Assert(BufferSizeInSamples <= (int32)SoundFrameCount); | |
} | |
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) | |
{ | |
// TODO(casey): Aspect ratio correction | |
// TODO(casey): Play with stretch modes | |
StretchDIBits(DeviceContext, | |
/* | |
X, Y, Width, Height, | |
X, Y, Width, Height, | |
*/ | |
0, 0, WindowWidth, WindowHeight, | |
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: | |
{ | |
OutputDebugStringA("WM_ACTIVATEAPP\n"); | |
} 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 | |
Win32FillSoundBuffer(win32_sound_output *SoundOutput, int SamplesToWrite, | |
game_sound_output_buffer *SourceBuffer) | |
{ | |
BYTE* SoundBufferData; | |
if (SUCCEEDED(GlobalSoundRenderClient->GetBuffer((UINT32)SamplesToWrite, &SoundBufferData))) | |
{ | |
int16* SourceSample = SourceBuffer->Samples; | |
int16* DestSample = (int16*)SoundBufferData; | |
for(int SampleIndex = 0; | |
SampleIndex < SamplesToWrite; | |
++SampleIndex) | |
{ | |
*DestSample++ = *SourceSample++; | |
*DestSample++ = *SourceSample++; | |
++SoundOutput->RunningSampleIndex; | |
} | |
GlobalSoundRenderClient->ReleaseBuffer((UINT32)SamplesToWrite, 0); | |
} | |
} | |
internal void | |
Win32ProcessKeyboardMessage(game_button_state *NewState, bool32 IsDown) | |
{ | |
Assert(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 | |
Win32ProcessPendingMessages(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; | |
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->Start, IsDown); | |
} | |
else if(VKCode == VK_SPACE) | |
{ | |
Win32ProcessKeyboardMessage(&KeyboardController->Back, IsDown); | |
} | |
} | |
bool32 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 real32 | |
Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End) | |
{ | |
real32 Result = ((real32)(End.QuadPart - Start.QuadPart) / | |
(real32)GlobalPerfCountFrequency); | |
return(Result); | |
} | |
internal void | |
Win32DebugDrawVertical(win32_offscreen_buffer *GlobalBackbuffer, | |
int X, int Top, int Bottom, uint32 Color) | |
{ | |
uint8 *Pixel = ((uint8 *)GlobalBackbuffer->Memory + | |
X*GlobalBackbuffer->BytesPerPixel + | |
Top*GlobalBackbuffer->Pitch); | |
for(int Y = Top; | |
Y < Bottom; | |
++Y) | |
{ | |
*(uint32 *)Pixel = Color; | |
Pixel += GlobalBackbuffer->Pitch; | |
} | |
} | |
inline void | |
Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer, | |
win32_sound_output *SoundOutput, | |
real32 C, int PadX, int Top, int Bottom, | |
DWORD Value, uint32 Color) | |
{ | |
Assert(Value < SoundOutput->SecondaryBufferSize); | |
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, | |
win32_sound_output *SoundOutput, real32 TargetSecondsPerFrame) | |
{ | |
// TODO(casey): Draw where we're writing out sound | |
int PadX = 16; | |
int PadY = 16; | |
int Top = PadY; | |
int Bottom = Backbuffer->Height - PadY; | |
real32 C = (real32)(Backbuffer->Width - 2*PadX) / (real32)SoundOutput->SecondaryBufferSize; | |
for(int MarkerIndex = 0; | |
MarkerIndex < MarkerCount; | |
++MarkerIndex) | |
{ | |
win32_debug_time_marker *ThisMarker = &Markers[MarkerIndex]; | |
Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->PlayCursor, 0xFFFFFFFF); | |
// Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->WriteCursor, 0xFFFF0000); | |
} | |
} | |
int CALLBACK | |
WinMain(HINSTANCE Instance, | |
HINSTANCE PrevInstance, | |
LPSTR CommandLine, | |
int ShowCode) | |
{ | |
LARGE_INTEGER PerfCountFrequencyResult; | |
QueryPerformanceFrequency(&PerfCountFrequencyResult); | |
GlobalPerfCountFrequency = PerfCountFrequencyResult.QuadPart; | |
// 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(); | |
WNDCLASSA WindowClass = {}; | |
Win32ResizeDIBSection(&GlobalBackbuffer, 1280, 720); | |
WindowClass.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC; | |
WindowClass.lpfnWndProc = Win32MainWindowCallback; | |
WindowClass.hInstance = Instance; | |
// WindowClass.hIcon; | |
WindowClass.lpszClassName = "HandmadeHeroWindowClass"; | |
// TODO(casey): How do we reliably query on this on Windows? | |
// TODO(casey): Let's think about running non-frame-quantized for audio latency... | |
// TODO(casey): Let's use the write cursor delta from the play cursor to adjust | |
// the target audio latency. | |
#define FramesOfAudioLatency 1 | |
#define MonitorRefreshHz 60 | |
#define GameUpdateHz (MonitorRefreshHz / 2) | |
real32 TargetSecondsPerFrame = 1.0f / (real32)GameUpdateHz; | |
if(RegisterClassA(&WindowClass)) | |
{ | |
HWND Window = | |
CreateWindowExA( | |
0, | |
WindowClass.lpszClassName, | |
"Handmade Hero", | |
WS_OVERLAPPEDWINDOW|WS_VISIBLE, | |
CW_USEDEFAULT, | |
CW_USEDEFAULT, | |
CW_USEDEFAULT, | |
CW_USEDEFAULT, | |
0, | |
0, | |
Instance, | |
0); | |
if(Window) | |
{ | |
// NOTE(casey): Since we specified CS_OWNDC, we can just | |
// get one device context and use it forever because we | |
// are not sharing it with anyone. | |
HDC DeviceContext = GetDC(Window); | |
win32_sound_output SoundOutput = {}; | |
// TODO(casey): Make this like sixty seconds? | |
SoundOutput.SamplesPerSecond = 48000; | |
SoundOutput.BytesPerSample = sizeof(int16)*2; | |
SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond; | |
SoundOutput.LatencySampleCount = FramesOfAudioLatency*(SoundOutput.SamplesPerSecond / GameUpdateHz); | |
Win32InitWASAPI(SoundOutput.SamplesPerSecond, SoundOutput.SecondaryBufferSize); | |
GlobalSoundClient->Start(); | |
GlobalRunning = true; | |
// TODO(casey): Pool with bitmap VirtualAlloc | |
int16 *Samples = (int16 *)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); | |
// TODO(casey): Handle various memory footprints (USING SYSTEM METRICS) | |
uint64 TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize; | |
GameMemory.PermanentStorage = VirtualAlloc(BaseAddress, (size_t)TotalSize, | |
MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); | |
GameMemory.TransientStorage = ((uint8 *)GameMemory.PermanentStorage + | |
GameMemory.PermanentStorageSize); | |
if(Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage) | |
{ | |
game_input Input[2] = {}; | |
game_input *NewInput = &Input[0]; | |
game_input *OldInput = &Input[1]; | |
LARGE_INTEGER LastCounter = Win32GetWallClock(); | |
int DebugTimeMarkerIndex = 0; | |
win32_debug_time_marker DebugTimeMarkers[GameUpdateHz] = {0}; | |
DWORD LastPlayCursor = 0; | |
bool32 SoundIsValid = false; | |
uint64 LastCycleCount = __rdtsc(); | |
while(GlobalRunning) | |
{ | |
// 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(NewKeyboardController); | |
// 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; | |
// 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; | |
} | |
} | |
// NOTE(casey): Compute how much sound to write and where | |
int SamplesToWrite = 0; | |
UINT32 SoundPaddingSize; | |
if (SUCCEEDED(GlobalSoundClient->GetCurrentPadding(&SoundPaddingSize))) | |
{ | |
SamplesToWrite = (int)(SoundOutput.SecondaryBufferSize - SoundPaddingSize); | |
if (SamplesToWrite > SoundOutput.LatencySampleCount) | |
{ | |
SamplesToWrite = SoundOutput.LatencySampleCount; | |
} | |
} | |
game_sound_output_buffer SoundBuffer = {}; | |
SoundBuffer.SamplesPerSecond = SoundOutput.SamplesPerSecond; | |
SoundBuffer.SampleCount = SamplesToWrite; | |
SoundBuffer.Samples = Samples; | |
game_offscreen_buffer Buffer = {}; | |
Buffer.Memory = GlobalBackbuffer.Memory; | |
Buffer.Width = GlobalBackbuffer.Width; | |
Buffer.Height = GlobalBackbuffer.Height; | |
Buffer.Pitch = GlobalBackbuffer.Pitch; | |
GameUpdateAndRender(&GameMemory, NewInput, &Buffer, &SoundBuffer); | |
Win32FillSoundBuffer(&SoundOutput, SamplesToWrite, &SoundBuffer); | |
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()); | |
// Assert(TestSecondsElapsedForFrame < TargetSecondsPerFrame); | |
while(SecondsElapsedForFrame < TargetSecondsPerFrame) | |
{ | |
SecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter, | |
Win32GetWallClock()); | |
} | |
} | |
else | |
{ | |
// TODO(casey): MISSED FRAME RATE! | |
// TODO(casey): Logging | |
} | |
LARGE_INTEGER EndCounter = Win32GetWallClock(); | |
real32 MSPerFrame = 1000.0f*Win32GetSecondsElapsed(LastCounter, EndCounter); | |
LastCounter = EndCounter; | |
win32_window_dimension Dimension = Win32GetWindowDimension(Window); | |
Win32DebugSyncDisplay(&GlobalBackbuffer, ArrayCount(DebugTimeMarkers), DebugTimeMarkers, | |
&SoundOutput, TargetSecondsPerFrame); | |
Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext, | |
Dimension.Width, Dimension.Height); | |
#if HANDMADE_INTERNAL | |
// NOTE(casey): This is debug code | |
{ | |
win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex++]; | |
if(DebugTimeMarkerIndex >= ArrayCount(DebugTimeMarkers)) | |
{ | |
DebugTimeMarkerIndex = 0; | |
} | |
UINT64 PositionFrequency; | |
UINT64 PositionUnits; | |
IAudioClock* AudioClock; | |
GlobalSoundClient->GetService(IID_PPV_ARGS(&AudioClock)); | |
AudioClock->GetFrequency(&PositionFrequency); | |
AudioClock->GetPosition(&PositionUnits, 0); | |
AudioClock->Release(); | |
Marker->PlayCursor = (DWORD)(SoundOutput.SamplesPerSecond * PositionUnits / PositionFrequency) % SoundOutput.SamplesPerSecond; | |
} | |
#endif | |
game_input *Temp = NewInput; | |
NewInput = OldInput; | |
OldInput = Temp; | |
// TODO(casey): Should I clear these here? | |
uint64 EndCycleCount = __rdtsc(); | |
uint64 CyclesElapsed = EndCycleCount - LastCycleCount; | |
LastCycleCount = EndCycleCount; | |
real64 FPS = 0.0f; | |
real64 MCPF = ((real64)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); | |
} | |
} | |
else | |
{ | |
// TODO(casey): Logging | |
} | |
} | |
else | |
{ | |
// TODO(casey): Logging | |
} | |
} | |
else | |
{ | |
// TODO(casey): Logging | |
} | |
return(0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment